CF1290C Prefix Enlightenment
题意
给一串长度为
N
N
N的
01
01
01串
S
S
S,以及
K
K
K个集合
A
i
A_i
Ai,每个集合有
c
i
c_i
ci个元素,每个元素
x
∈
[
1
,
N
]
x∈[1, N]
x∈[1,N]
题目保证任意三个集合
A
i
∩
A
j
∩
A
k
=
∅
A_i ∩A_j ∩ A_k = ∅
Ai∩Aj∩Ak=∅,你可以选择一个集合,然后令
S
S
S中集合
A
i
A_i
Ai元素下标的
01
01
01
现在求
S
S
S串中
1
−
i
1-i
1−i都为
1
1
1时所需要的最小集合数,
i
∈
[
1
,
N
]
i∈[1,N]
i∈[1,N]
题解
题目中给的
A
i
∩
A
j
∩
A
k
=
∅
A_i ∩A_j ∩ A_k = ∅
Ai∩Aj∩Ak=∅很关键
他告诉我们:每个下标最多只会出现在两个集合里面
现在我们假定每个下标都恰好出现在两个集合中
那么对于
S
i
S_i
Si来说:
①
S
i
=
1
S_i=1
Si=1
本来就是1,那么可以不反转或者反转两次
②
S
i
=
0
S_i=0
Si=0
当前位为0,那么必须要反转一次,那么就是两个集合二选一
然后这里就用到了种类并查集,对于每一个集合,都有选和不选两种对立情况,因此把它们分成两类
p
r
e
[
i
]
pre[i]
pre[i]和
s
i
z
[
i
]
siz[i]
siz[i]表示
i
i
i所在的集合和对应集合大小
i
i
i表示选了第
i
i
i个集合,
i
+
K
i+K
i+K表示不选第
i
i
i个集合
因此
s
i
z
[
i
]
siz[i]
siz[i]初始值为
s
i
z
[
i
]
=
(
i
<
=
K
)
siz[i]=(i<=K)
siz[i]=(i<=K)
然后注意上面我们都是假定一个下标对应两个集合,但是有时候可能只有
1
1
1个,所以再继续分类讨论一下就行
最终就是遍历串
S
S
S的过程中动态维护
a
n
s
ans
ans
这里比较难理解,很多通过的代码都是动态维护的,我注释写在代码里面了
这里
D
S
U
DSU
DSU也能做,下次一定!
代码
#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;
}