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;
}

posted @ 2022-04-18 21:11  Bellala  阅读(20)  评论(0)    收藏  举报