題目
給出一個長度爲n(n<=3e5)的01串S,
給出k(k<=3e5)個集合,每個集合裏是串裏的一些位置的下標,保證對於任意三個不同的集合i,j,k,
你可以指定一個集合,將集合內的01全部取反,視爲依次操作
問,對於每個i,需要至少多少次操作,可以將前綴i個數都置1(此時不用關心後面的數是0還是1)
題目保證有解
思路來源
https://blog.csdn.net/wyy603/article/details/104156509
題解
考慮,三個集合相交爲空,說明每個元素最多出現在兩個集合裏
沒出現,顯然不用管,
維護種類並查集,1到k是不選第i個集合,代價爲0,k+1到2k是選第i個集合,代價爲1
①出現在一個集合裏時,如果這一位本身爲1,就不選,否則必選
②出現在兩個集合裏時,不妨設x和y,如果爲1,同時選x和y,或同時不選;如果爲0,同時選x和y+k,或同時選x+k和y
對於一個固定大小的集合,選其或者其對立都是可行的方案,二者取小代價即可
對於②類型操作,考慮先減去上一次兩個獨立集合的貢獻,然後將其合併,再加上一個獨立集合的貢獻
對於①類型的操作,必選這個比較難討論,因爲要涉及到後續的集合合併
這裏的處理方式是,維護一個0節點,選0節點的代價爲INF,
將x和x+k兩個集合中一定不用的那個集合(不妨設爲x)和0節點合併,
選x的代價爲INF,就自然不會選INF了,達到了強制選x+k的目的
代碼
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+10,M=2*N,INF=0x3f3f3f3f;
int n,k,c,x,y,par[M],cost[M],ans;
vector<int>a[N];//a[i][j]表示i受a[i][j]個集合控制
char s[N];
int find(int x){
return par[x]==x?x:par[x]=find(par[x]);
}
void merge(int x,int y){
x=find(x),y=find(y);
if(x==y)return;
par[x]=y;
cost[y]+=cost[x];
}
int f(int x){
return min(cost[find(x)],cost[find(x+k)]);
}
int main(){
scanf("%d%d",&n,&k);
scanf("%s",s+1);
for(int i=1;i<=k;++i){
scanf("%d",&c);
for(int j=1;j<=c;++j){
scanf("%d",&x);
a[x].push_back(i);
}
}
//拆點並查集 1-n爲不用 n+1 -2n爲用
for(int i=1;i<=2*k;++i){
par[i]=i;
cost[i]= i>k;
}
par[0]=0;
cost[0]=INF;
for(int i=1;i<=n;++i){
int sz=(int)a[i].size();
if(sz==1){
int x=a[i][0];
int ban=(s[i]=='1')*k+x;
ans-=f(x);
merge(ban,0);
ans+=f(x);
}
else if(sz==2){
int x=a[i][0],y=a[i][1];
if(s[i]=='1'){
if(find(x)!=find(y)){
ans-=f(x)+f(y);
merge(x,y);
merge(x+k,y+k);
ans+=f(x);
}
}
else{
if(find(x)!=find(y+k)){
ans-=f(x)+f(y);
merge(x,y+k);
merge(x+k,y);
ans+=f(x);
}
}
}
printf("%d\n",ans);
}
return 0;
}