[带权扩展域并查集] Codeforces 1290C Prefix Enlightenment
好题,收藏下。
题目大意
给定一个长度为 \(n\) 的 01 串 \(S\),和 \(k\) 个 \(S\) 的下标子集,且任意三个子集的交集为空集。一次操作可以选择一个子集,将子集中的下标对应的 \(S_i\)取反。令\(m_i\)为让\(S_{1 \sim i}=1\) 的最少操作次数,求出所有的\(m_i\),保证有方案。
\(1\leq n,k \leq 3 \times 10^5\)。
题解
任意三个子集的交集为空集,显然,一个点最多只能在两个集合中出现,这样所有集合的size之和是\(O(n)\)的。
一个在两个集合中出现的点\(i\)相当于连接了2个集合\(u_i\)和\(v_i\),这是一个图。
不妨把整张图染成选和不选两种颜色。
若\(S_i=0\),则\(i\)的状态要改变,可以发现,\(u_i\)和\(v_i\)一定异色。
若\(S_i=1\),则\(i\)的状态不变,可以发现,\(u_i\)和\(v_i\)一定同色。
接下来就用扩展域并查集来做,一个颜色的点的权值是\(0\),另一个颜色的点的权值是\(1\),取并查集合并后两个集合的权值的最小值即为答案。
如果\(i\)在任何一个给出的集合中都未出现过,则我们无法去修改\(S_i\),但因为题目保证有解,所以不用去管。
但若某个\(i\)只在一个集合中出现,怎么去强制选点?领略到了一个神奇的做法,可以建一个点权为无穷大的虚点,把该点和虚点连接。
Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
using namespace std;
template<typename elemType>
inline void Read(elemType &T){
elemType X=0,w=0; char ch=0;
while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
T=(w?-X:X);
}
vector<int> G[300010];
char Str[300010];
int S[600010],Rank[600010],Value[600010];
int N,K,Ans=0;
int Find(int u){
if(S[u]==0) return u;
return S[u]=Find(S[u]);
}
void union_set(int u,int v){
if((u=Find(u))==(v=Find(v))) return;
else if(Rank[u]>Rank[v]) {S[v]=u;Value[u]+=Value[v];}
else if(Rank[u]<Rank[v]) {S[u]=v;Value[v]+=Value[u];}
else {S[v]=u;Rank[u]++;Value[u]+=Value[v];}
}
inline int Val(int u){
return min(Value[Find(u)],Value[Find(u+K)]);
}
int main(){
Read(N);Read(K);
scanf("%s",Str+1);
for(register int i=1;i<=K;++i){
Value[i+K]=1;
int Len;Read(Len);
for(register int j=1;j<=Len;++j){
int x;Read(x);
G[x].push_back(i);
}
}
Value[K*2+1]=0x3f3f3f3f;
for(register int i=1;i<=N;++i){
if(G[i].size()==1){
int u=G[i][0];
if(Str[i]=='1') u+=K;
Ans-=Val(G[i][0]);
union_set(u,K*2+1);
Ans+=Val(G[i][0]);
}
else if(G[i].size()==2){
int u=G[i][0],v=G[i][1];
if(Str[i]=='0' && Find(u)!=Find(v+K)){
Ans-=Val(u)+Val(v);
union_set(u,v+K);union_set(u+K,v);
Ans+=Val(u);
}
else if(Str[i]=='1' && Find(u)!=Find(v)){
Ans-=Val(u)+Val(v);
union_set(u,v);union_set(u+K,v+K);
Ans+=Val(u);
}
}
printf("%d\n",Ans);
}
return 0;
}