zoukankan      html  css  js  c++  java
  • 扩展域并查集+图论——cf1290C 好题

    一道很好的题了,具体题解可以看b站的讲解。。

    拆点的思想有一种2sat的感觉

    /*
    给定一组开关的集合,每个开关最多被两个集合包含,对集合操作一次则所有集合内的开关状态变化 
    现在要将前i个开关状态切换到开,问最少要操作几次集合,求出i从1到n的每个答案 
    
    一些性质:每个集合要么被操作一次,要么不被操作(两次操作等于不操作)
    那么我们将每个集合拆点,a表示操作,b表示不操作
     
    再看每个开关,设该开关被集合i,j所包含
        初始状态0:要么i操作,j不操作(在ia和jb之间连一条无向边) 
                   要么i不操作,j操作(在ib和ja之间连一条无向边)
        初始状态1:要么i操作,j操作(在ia和ja之间连一条无向边) 
                   要么i不操作,j不操作(在ib和jb之间连一条无向边)
    
    具体实现:从1->n枚举所有点i 
        1.取消之前选择带来的贡献 
        2.增加强制关系    
            i 只有一个集合:不用连边,并且只能有一个选择(特判一下即可)
            i 有两个集合:连边后再选择较优的 
        3.在新的强制关系下再进行选择,更新答案 
    */
    #include<bits/stdc++.h>
    using namespace std;
    #define N 600005
    
    int n,k,l[N],r[N];
    char s[N];
    
    int F[N],size[N];
    int find(int x){
        return F[x]==x?x:F[x]=find(F[x]);
    }
    void bing(int x,int y){
        int fx=find(x),fy=find(y);
        //一个是0的话就要把父亲设为0 
        if(!fx){
            F[fy]=0; 
            return;
        }
        if(!fy){
            F[fx]=0;
            return;
        }
        
        if(fx!=fy){
            size[fx]+=size[fy];
            F[fy]=fx;
        } 
    }
    int calc(int i){//对第i个集合,是选还是不选 
        int u=i,v;
        if(u>k)v=u-k;
        else v=u+k;
        int fu=find(u),fv=find(v);
        if(!fu || !fv)return size[fu+fv];//如果有一个不能选,那么直接选另一个 
        return min(size[fu],size[fv]);
    }
    
    int main(){
        cin>>n>>k;
        scanf("%s",s+1);
        for(int i=1;i<=k*2;i++)F[i]=i;
        for(int i=1;i<=k;i++)size[i]=1;
        for(int i=1;i<=k;i++){
            int m;cin>>m;
            while(m--){
                int x;cin>>x;
                if(l[x])r[x]=i;
                else l[x]=i;
            }
        }
        
        int ans=0; 
        for(int i=1;i<=n;i++){
            if(l[i] && r[i]){//有两个集合可选 
                int u=l[i],v=r[i];
                if(s[i]=='1'){
                    if(find(u)!=find(v)){
                    //如果这种强制关系还没被建立,那么现在必须建立起来,并且再去选择策略 
                    //反之如果已经建立好了,表示这个点不管选哪种策略都不重要了,其贡献也不用统计 
                        ans-=calc(u);ans-=calc(v);//先撤销u,v集合选择情况的贡献
                        bing(u,v);bing(u+k,v+k);//连边,即选u的同时必须选v,反之亦然连边 
                        ans+=calc(u);//再把这个影响加回去 
                    }    
                }
                else{
                    if(find(u)!=find(v+k)){
                        ans-=calc(u);ans-=calc(v);
                        bing(u,v+k);bing(u+k,v);
                        ans+=calc(u);
                    }
                } 
            }
            else if(l[i] && !r[i]){//只有一个集合,必选 
                int u=l[i];
                ans-=calc(u);
                if(s[i]=='1')F[find(u)]=0;
                else F[find(u+k)]=0;
                ans+=calc(u); 
            }
            cout<<ans<<'
    ';
        }
        
    } 
  • 相关阅读:
    第一个反汇编程序
    边缘网关协议(BGP)
    Servlet 学习小结之doPost()方法和doGet()方法
    extern "C"
    工欲善其事 必先利其器
    我是一个混蛋程序员
    KMP 算法——C
    二分查找——C语言
    大整数加法——C语言
    子字符串查找——C语言
  • 原文地址:https://www.cnblogs.com/zsben991126/p/12267233.html
Copyright © 2011-2022 走看看