码迷,mamicode.com
首页 > 其他好文 > 详细

扩展域并查集+图论——cf1290C 好题

时间:2020-02-06 01:00:54      阅读:72      评论:0      收藏:0      [点我收藏+]

标签:find   bing   b站   using   include   %s   else   for   namespace   

一道很好的题了,具体题解可以看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<<\n;
    }
    
} 

 

扩展域并查集+图论——cf1290C 好题

标签:find   bing   b站   using   include   %s   else   for   namespace   

原文地址:https://www.cnblogs.com/zsben991126/p/12267233.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!