题意
有一个长度为n的01字符串和m个子集,保证任意3个子集∩为空,问将长度从1到n的字符串前缀全部变成1的最小步数,其中每一步可以做的事情是选择一个子集,将这些位置上的字符全部取反。保证可以将整个字符串变成全1状态
数据范围
解法
首先观察到,任意3个子集的交为空说明任意一个位置只会最多在两个子集中出现,所以对于一个位置,如果它出现在两个子集中,并且现在是0,那么我一定会取两个子集中的某一个,如果它现在是1,我有可能一个不取,也可能取两个。然后如果只有一个子集包含它,那么直接分析要不要取这个子集就行了。但是取一个子集可能会影响其它的位置,这怎么办呢?
考虑将子集看作点,然后枚举1到n,考虑两个有关的子集是不是颜色相同(假设取相当于一种颜色,不去相当于一种颜色),然后对于一个子集建两个点,一个表示取,一个表示不取,然后根据对应关系连边(这里具体看代码,是用并查集维护的),一开始所有点都不取。然后在两个符合要求的联通块中选一个较小的取。
代码是copy的
#include<stdio.h>
#define lim 1000000
int a[1000002][2],n,m,f[2000002],s[2000002];
char c[1000002];
int ans;
inline int gf(int p){return f[p]==p?p:(f[p]=gf(f[p]));}
inline int Min(int p,int q){return p<q?p:q;}
inline void add(int p,int q,bool typ){
p=gf(p);q=gf(q);//首先要找到两个点的代表元(和它们同色的编号最小的点,这里不考虑状态)
if(p>=lim)p-=lim,typ=!typ;
if(q>=lim)q-=lim,typ=!typ;
if(p==q)return;//如果是同一个,说明已经同色了,没有必要做
ans-=Min(s[p],s[p+lim])+Min(s[q],s[q+lim]);
if(typ){//如果本来就是1,那么两个子集的颜色是一样的
f[q]=p;s[p]+=s[q];
f[q+lim]=p+lim;s[p+lim]+=s[q+lim];
}else{//否则是不一样的
f[q+lim]=p;s[p]+=s[q+lim];
f[q]=p+lim;s[p+lim]+=s[q];
}ans+=Min(s[p],s[p+lim]);
}
int main(){
scanf("%d%d%s",&m,&n,c+1);
for(int i=1;i<=2000000;i++)f[i]=i;
for(int i=1;i<=n;i++)s[i]=1,s[i+lim]=0;
s[0]=lim;s[lim]=-lim;ans=-lim;
for(int i=1;i<=n;i++){
int k;scanf("%d",&k);
while(k--){
int p;scanf("%d",&p);
if(a[p][0])a[p][1]=i;
else a[p][0]=i;
}
}for(int i=1;i<=m;i++){
if(a[i][0])add(a[i][0],a[i][1],c[i]=='1');
printf("%d\n",ans+lim);
}
}