CF1290C Prefix Enlightenment (并查集)
大意: 给定长$n$的01串$s$, 给定$k$个集合$A_1,...,A_k$,保证任意三个集合交集为空. 每次操作选择一个集合,翻转$s$中对应位置. 定义$m_i$为使前$i$个位置全为$1$所需的最少操作数(题目数据保证每个$m_i$都存在), 求所有$m_i$的值.
显然每个位置最多属于两个集合.
假设只对应一个集合$X$, 那么$X=\overline{s_i}$
假设对应集合$X,Y$, 那么$X\oplus Y=\overline{s_i}$
所以题目就等价于给定一些异或方程组, 求最少多少个变量值为1.
考虑把每个集合拆成两个点$X_1,X_0$表示选或不选.
对于$X\oplus Y=1$, 就连边$(X_1,Y_0),(X_0,Y_1)$
对于$X\oplus Y=0$, 就连边$(X_1,Y_1),(X_0,Y_0)$
对于$X=\overline{s_i}$, 有一个技巧是设一个权值无穷大的点, 限制$X$不能选$s_i$.
可以用并查集实现, 选一个权值和最小的连通块即可.
#include <iostream> #include <algorithm> #include <cstdio> #include <vector> #define REP(_i,_a,_n) for(int _i=_a;_i<=_n;++_i) #define PER(_i,_a,_n) for(int _i=_n;_i>=_a;--_i) #define pb push_back using namespace std; typedef long long ll; typedef pair<int,int> pii; const int P = 1e9+7, INF = 0x3f3f3f3f; const int N = 1e6+10; int n, k, fa[N], val[N]; char s[N]; vector<int> g[N]; int Find(int x) {return fa[x]?fa[x]=Find(fa[x]):x;} void add(int x, int y) { x = Find(x), y = Find(y); if (x!=y) fa[x]=y,val[y]+=val[x]; } int calc(int x) { return min(val[Find(x)],val[Find(x+k)]); } int main() { scanf("%d%d%s",&n,&k,s+1); REP(i,1,k) { int x, t; scanf("%d",&x); while (x--) { scanf("%d", &t); g[t].pb(i); } } REP(i,k+1,2*k) val[i] = 1; val[2*k+1] = INF; int ans = 0; REP(i,1,n) { if (g[i].size()==1) { ans -= calc(g[i][0]); if (s[i]=='1') add(g[i][0]+k,2*k+1); else add(g[i][0],2*k+1); ans += calc(g[i][0]); } else if (g[i].size()==2) { int x = g[i][0], y = g[i][1]; if (Find(x)!=Find(y)&&Find(x)!=Find(y+k)) { ans -= calc(x)+calc(y); if (s[i]=='1') add(x,y),add(x+k,y+k); else add(x+k,y),add(x,y+k); ans += calc(x); } } printf("%d\n", ans); } }