CF1290C Prefix Enlightenment
题意
给一串长度为
的
串
,以及
个集合
,每个集合有
个元素,每个元素
题目保证任意三个集合
,你可以选择一个集合,然后令
中集合
元素下标的
现在求
串中
都为
时所需要的最小集合数,
题解
题目中给的
很关键
他告诉我们:每个下标最多只会出现在两个集合里面
现在我们假定每个下标都恰好出现在两个集合中
那么对于
来说:
①
本来就是1,那么可以不反转或者反转两次
②
当前位为0,那么必须要反转一次,那么就是两个集合二选一
然后这里就用到了种类并查集,对于每一个集合,都有选和不选两种对立情况,因此把它们分成两类
和
表示
所在的集合和对应集合大小
表示选了第
个集合,
表示不选第
个集合
因此
初始值为
然后注意上面我们都是假定一个下标对应两个集合,但是有时候可能只有
个,所以再继续分类讨论一下就行
最终就是遍历串
的过程中动态维护
这里比较难理解,很多通过的代码都是动态维护的,我注释写在代码里面了
这里
也能做,下次一定!
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX = 3e5 + 10;
int N, K;
char s[MAX];
int col[MAX][3], pre[MAX << 1], siz[MAX << 1], ans;
int find(int x) { return x == pre[x] ? x : pre[x] = find(pre[x]); }
void merge(int x, int y) {
x = find(x), y = find(y);
if (x != y) pre[x] = y, siz[y] += siz[x];//再维护一个siz
}
//查找选x和不选x两种里面花费最小的
int calc(int x) { return min(siz[find(x)], siz[find(x + K)]); }
void solve(int i) {
int cnt = col[i][0];
if (cnt == 2) {//对应两个集合
int x = col[i][1], y = col[i][2];
if (s[i] == '1') {
//合并(x, y), (x + K, y + K)
//(x, y) => 选了x和y
//(x + K, y + K) => 两个都不选
if (find(x) == find(y)) return;
//这里每一个集合在第一次贡献前,减去的答案都是0
//因为siz[x + K] = 0 => calc(x) = 0
//之后的每次贡献, 都会先减去上一次的贡献, 然后加入本次贡献
ans -= calc(x) + calc(y);//先减去合并前的贡献
merge(x, y), merge(x + K, y + K);//合并
ans += calc(x);//再加上合并后的贡献
}
else {
//merge (x, y + K) (x + K, y)
//这里同理
if (find(x) == find(y + K)) return;
ans -= calc(x) + calc(y);
merge(x, y + K), merge(x + K, y);
ans += calc(x);
}
}
else if (cnt == 1) {//对应1个集合
int x = col[i][1];
if (s[i] == '1') {
//原来就是1了, 所以不用翻转集合
//x表示选择了, 然后翻转
//那么就是舍去x,我们把他和集合0合并
if (find(x) == find(0)) return;
//集合0是不会有贡献的, 里面保存的都是不可能的情况
ans -= calc(x);
merge(x, 0);
ans += calc(x);//因为这里必然不选x, 而find(x) = 0, 因此需要令siz[0] = inf
}
else {
//需要x, 舍弃x + K, 合并(x + K, 0)
if (find(x + K) == find(0)) return;
ans -= calc(x);
merge(x + K, 0);
ans += calc(x);
}
}
}
int main() {
scanf("%d%d%s", &N, &K, s + 1);
for (int i = 1; i <= K; i++) {
int c; scanf("%d", &c);
for (int j = 1; j <= c; j++) {
int x; scanf("%d", &x);
col[x][++col[x][0]] = i;
}
}
for (int i = 1; i <= (K << 1); i++) pre[i] = i, siz[i] = i <= K;
siz[0] = 1e9;//这里后面会解释
//0集合保留永远不会再用的点
for (int i = 1; i <= N; i++) {
solve(i);
printf("%d\n", ans);
}
return 0;
}