cf #616 (Div. 2) E. Prefix Enlightenment 拆点并查集

题目链接: http://codeforces.com/contest/1291/problem/E

题意:

你现在有一个 nn 位的 0101SS ,和 kk 个集合,每个集合里会有 1,2,3,4,.....,n{1,2,3,4,.....,n} 中的若干个数字,并且保证每个数字只会在最多两个集合中出现。

当你某次选择某一个集合 xx 时,串 SS 中这个集合中的所有数字所在的位置都会翻转( 01,100→1,1→0)。现在让你找出将串中第 11 位至第 ii 位都翻转为 11 的最少选择的集合数是多少,对 ii1n1-n 的答案均输出,保证答案一定有解。

做法:

因为 nn 的范围很大所以只能使用 nlognnlog n 的做法,也是参考了别人的代码后自己理解着写的。

用的是所谓的拆点并查集的方法,我们先对于同一个位置 xx 来进行讨论

如果它是 00
没有集合拥有位置 xx 的情况不存在。
有一个集合 AA 拥有位置 xx 时,这个集合一定要被强制选择。
有两个集合 A,BA,B 拥有位置 xx 时,这两个集合中一定要有一个要被选择。

如果它是 11
没有集合拥有位置 xx ,则可以不用看它。
有一个集合 AA 拥有位置 xx 时,这个集合一定要被强制不能选择。
有两个集合 A,BA,B 拥有位置 xx 时,这两个集合要么都要选,要么都不选。

然后,我们可以将每个集合拆成两个点,分别为选,和不选。在上面的讨论中,我们就可以将每次的情况进行处理,但是好像强制选择或者不选择比较难处理,所以加了一个超级点(即结果很大的一定不会被选择的情况)。

#include <bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const int maxn=600005;
int n,k,a[maxn],fa[maxn],val[maxn],big;
vector<int> ve[maxn];
int fin(int x){
    return fa[x]==x?x:fa[x]=fin(fa[x]);
}
int uni(int x,int y){
    int fx=fin(x),fy=fin(y);
    if(fx!=fy) fa[fx]=fy,val[fy]+=val[fx];
}
int gmin(int x){
    return min(val[fin(x)],val[fin(x+k)]);
}
int main() {
    scanf("%d%d",&n,&k);
    rep(i,1,n) scanf("%1d",&a[i]);
    rep(i,1,k){
        int x,y; scanf("%d",&x);
        while(x--) scanf("%d",&y),ve[y].push_back(i);
    }
    //1-n表示不取 n+1-2*n表示要取
    rep(i,1,2*k+1) fa[i]=i,val[i]=(i<=k);
    big=2*k+1;
    val[big]=(int)1e9;
    int ans=0;
    rep(i,1,n){
        if(ve[i].size()==1){
            // 如果a[i]为0则强制要取该集合为1
            int tmp=ve[i][0]+k*(a[i]==0);
            ans-=gmin(ve[i][0]);
            uni(tmp,big);
            ans+=gmin(ve[i][0]);
        }
        else if(ve[i].size()==2){
            int x=ve[i][0],y=ve[i][1];
            if(a[i]==0&&fin(x)!=fin(y+k)){
                ans=ans-gmin(x)-gmin(y);
                uni(x,y+k),uni(y,x+k);
                ans+=gmin(x);
            }
            if(a[i]==1&&fin(x)!=fin(y)){
                ans=ans-gmin(x)-gmin(y);
                uni(x,y),uni(x+k,y+k);
                ans+=gmin(x);
            }
        }
        printf("%d\n",ans);
    }

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