Description
题库链接
给出长度为 \(n\) 的 01 串 \(S\),给定 \(k\) 个下标集合 \(A_1,\cdots,A_k\),保证任意三个集合交集为空。每次操作选择一个集合,翻转 \(S\) 中对应位置。定义 \(m_i\) 为使前 \(i\) 个位置全为 \(1\) 所需的最少操作数(题目数据保证每个 \(m_i\) 都存在),求所有 \(m_i\) 的值。
\(1\leq n,k\leq 3\times 10^5\)
Solution
容易发现一个下标最多只会出现在两个集合中。
假设只存在于一个集合中时,若该位为 \(1\),那么这个集合一定不会被选;如果这位置为 \(0\),那么这个集合被选。
同样若这个位置存在于两个集合时,为 \(1\) 时,两个集合同选或同时不选;为 \(0\) 时,一个选择一个不选。
基于上述原因,我们考虑维护一个带权并查集,每个集合拆成两个意思是选或者不选。
从 \(1\) 到 \(n\) 枚举位置,对每一位进行讨论。同时需要开一个虚点权值为 \(\text{inf}\),将不会选到的状态(节点)连上去。
Code
#include
using namespace std;
const int N = 3e5+5, inf = 1e9;
int n, kk, s, x, k[N][2];
char ch[N];
int fa[N<<1], val[N<<1];
int find(int u) {return fa[u] ? fa[u] = find(fa[u]) : u; }
void uni(int a, int b) {
if (find(a) == find(b)) return;
val[find(b)] += val[find(a)];
fa[find(a)] = find(b);
}
int mix(int a) {return min(val[find(a)], val[find(a+kk)]); };
int main() {
scanf("%d%d%s", &n, &kk, ch+1);
for (int i = 1; i <= kk; i++) {
scanf("%d", &s);
while (s--) {
scanf("%d", &x);
k[x][bool(k[x][0])] = i;
}
}
for (int i = 1; i <= kk; i++) val[i] = 1;
val[kk+kk+1] = inf;
int ans = 0;
for (int i = 1; i <= n; i++) {
if (k[i][0] == 0) goto qwq;
if (ch[i] == '1') {
if (k[i][1]) {
if (find(k[i][0]) == find(k[i][1])) goto qwq;
ans -= mix(k[i][0])+mix(k[i][1]);
uni(k[i][0], k[i][1]), uni(k[i][0]+kk, k[i][1]+kk);
ans += mix(k[i][0]);
} else {
ans -= mix(k[i][0]);
uni(k[i][0], kk+kk+1);
ans += mix(k[i][0]);
}
} else {
if (k[i][1]) {
if (find(k[i][0]) == find(k[i][1]+kk)) goto qwq;
ans -= mix(k[i][0])+mix(k[i][1]);
uni(k[i][0], k[i][1]+kk), uni(k[i][0]+kk, k[i][1]);
ans += mix(k[i][0]);
} else {
ans -= mix(k[i][0]);
uni(k[i][0]+kk, kk+kk+1);
ans += mix(k[i][0]);
}
}
qwq :
printf("%d\n", ans);
}
return 0;
}