cf1257 E. The Contest
题意:
把某个n的排列放入三个集合中,一次操作可以改变一个数所属的集合。要求进行尽量少的操作,使小于等于某 x 的数都在集合1中,大于等于某 y 的数都在集合3中,x 思路: 法一: 给三个队列各排好序,然后再连接到一起,求一个LIS 。通过LIS得到长度(len)一定是保持最大不会动的位置,位置是固定。那么还剩n-len个,就是答案 法二:自己想的捏 设集合1中的最大数是 \(i\),集合2中的最大数是 \(j\),那么 \(0\le i\le j\le n\) 考虑 若数字 若数字 若数字 过程中所有 固定 若 若 若 对所有 记录前缀改变量最小值,用 手算一下样例1的所有f就懂了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] 更新答案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;
}