[带权扩展域并查集] 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;
}
posted @ 2020-02-20 10:46  AE酱  阅读(173)  评论(0编辑  收藏  举报