1. 程式人生 > 其它 >cf1257 E. The Contest

cf1257 E. The Contest

題意:

把某個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

    初始在集合1中,則 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)++

對所有 if(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;
}