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)++。
对所有 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;
}

浙公网安备 33010602011771号