Codeforces Round #616 (Div. 1) C.Prefix Enlightenment(种类并查集)

题目

给出一个长度为n(n<=3e5)的01串S,

给出k(k<=3e5)个集合,每个集合里是串里的一些位置的下标,保证对于任意三个不同的集合i,j,k,A_{i} \cap A_{j} \cap A_{k} = \phi

你可以指定一个集合,将集合内的01全部取反,视为依次操作

问,对于每个i,需要至少多少次操作,可以将前缀i个数都置1(此时不用关心后面的数是0还是1)

题目保证有解

思路来源

https://blog.csdn.net/wyy603/article/details/104156509

题解

考虑A_{i} \cap A_{j} \cap A_{k} = \phi,三个集合相交为空,说明每个元素最多出现在两个集合里

没出现,显然不用管,

 

维护种类并查集,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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章