cf1257 E. The Contest
阿新 • • 發佈:2022-04-18
題意:
把某個n的排列放入三個集合中,一次操作可以改變一個數所屬的集合。要求進行儘量少的操作,使小於等於某 x 的數都在集合1中,大於等於某 y 的數都在集合3中,x<y
思路:
法一:
給三個佇列各排好序,然後再連線到一起,求一個LIS 。通過LIS得到長度(len)一定是保持最大不會動的位置,位置是固定。那麼還剩n-len個,就是答案
法二:自己想的捏
設集合1中的最大數是 \(i\),集合2中的最大數是 \(j\),那麼 \(0\le i\le j\le n\)
f(i,j)
表示運算元,可以先求出 f(0,0~n)
考慮 f(i-1,)
到 f(i,)
如何變化。省略第一維。
-
若數字
i
i
本來要分到集合2,現在要留在集合1,所以f(i~n)--
; -
若數字
i
初始在集合2中,則i
本來要留在集合2,現在要去集合1,所以f(i~n)++
; -
若數字
i
初始在集合3中,則i
本來要分到集合2,現在要去集合1,所以不變。
過程中所有 f
的最小值就是答案。區間修改+區間最值應該上線段樹吧。但其實可以優化到線性,不需要線段樹:
固定 i
,考慮 f(i)
如何變化。首先數字 i+1
是不會影響到 f(i)
的。而在遍歷數字 x=1~i
的過程中,
-
若
x
初始在集合1中,則f(i)
不變; -
若
x
初始在集合2中,則f(i)--
-
若
x
初始在集合3中,則f(i)++
。
對所有 i
,f(i)
的改變過程是一樣的,大家一起不變/+1/-1。區別只是啥時候就再也不能改了而已
記錄字首改變數最小值,用 f(i)-mn[i]
更新答案
手算一下樣例1的所有f就懂了
const signed N = 2e5 + 3; int n, f[N], p[N]; //i初始位於哪個集合 int d, mn; signed main() { iofast; int k1, k2, k3, x; cin >> k1 >> k2 >> k3; n = k1 + k2 + k3; int ans = f[0] = k1 + k2; while(k1--) cin >> x, p[x] = 1; while(k2--) cin >> x, p[x] = 2; while(k3--) cin >> x, p[x] = 3; //求f(0,0~n) for(int i = 1; i <= n; i++) if(p[i] == 1) f[i] = f[i-1]; else if(p[i] == 2) f[i] = f[i-1] - 1; else if(p[i] == 3) f[i] = f[i-1] + 1; for(int i = 1; i <= n; i++) { if(p[i] == 1) d--; else if(p[i] == 2) d++; mn = min(mn, d); ans = min(ans, f[i] + mn); } cout << ans; }