二分图匹配——最大独立集||最大团 [HEOI2012]朋友圈

这道题我们可以仔细思考一下 

对于A国,我们分成奇数和偶数两类点,我们发现边全都是在两类点之 间的,同类点之间没有边。 再看B国,是我们发现同类点之间两两有边。 所以说我们需要将b国构成的图求一个补图,不就变成了二分图了吗? 

性质:二分图的最大独立集等于它补图的最大团 

所谓最大团, 其实就是找一个最大完全子图,最大就是包含的点最多 最大独立集=总点数-最大匹配数。

 废话不多说,上代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
const int maxn=5005;
const int maxm=6000000;
using namespace std;
int na,nb,m,ans,tot,cnt,now;
int a[maxn],b[maxn],mod[maxn],result[maxn];//mod为补图 
int num_edge,head[maxn];
bool map[maxn][maxn],tag[maxn],use[maxn];
struct Edge{
    int next,to;
}e[maxm];
void add_edge(int from,int to){
    e[++num_edge].next=head[from];
    e[num_edge].to=to;
    head[from]=num_edge;
}
bool dfs(int a){
    if(!tag[a]) return false;
    for(int i=head[a];i;i=e[i].next){
        if(tag[e[i].to]&&!use[e[i].to]){
            use[e[i].to]=true;
            if(result[e[i].to]==0||dfs(result[e[i].to])){
                result[e[i].to]=a;
                return true;
            }
        }
    }
    return false;
}
int main(){
    int t;
    cin>>t;
    for(int i=1;i<=t;i++){
        cin>>na>>nb>>m;
        for(int i=1;i<=na;i++){
            cin>>a[i];
        }
        for(int i=1;i<=nb;i++){
            cin>>b[i];
        }
        for(int i=1,u,v;i<=m;i++){
            cin>>u>>v;
            map[u][v]=true;
        }
        for(int i=1;i<=nb;i++){
            if(b[i]%2==1){
                mod[++tot]=i;
                for(int j=1;j<=nb;j++){
                    if(b[j]%2==0){
                        int cnt=b[i]|b[j],sum=0;
                        for(;cnt;cnt>>=1) if(cnt&1) sum++;
                        if(sum%2==0) add_edge(i,j);
                    }
                }
            }
        }
        for(int i=1;i<=nb;i++) tag[i]=true;
        memset(result,0,sizeof(result));
        now=0;
        for(int i=1;i<=tot;i++){
            memset(use,false,sizeof(use));
            if(dfs(mod[i])) now++;
        }
        ans=nb-now;//求补图过程 最大独立集=总点数-最大匹配数。
        for(int i=1;i<=na;i++){
            memset(tag,false,sizeof(tag));
            memset(result,0,sizeof(result));
            int sum=0;
            for(int j=1;j<=nb;j++) if(map[i][j]) tag[j]=true,sum++;
            now=0;
            for(int j=1;j<=tot;j++){
                memset(use,false,sizeof(use));
                if(tag[mod[j]]&&dfs(mod[j])) now++;
            }
            ans=max(ans,sum-now+1);
        }
        int sum=0;
        for(int i=1;i<=na;i++) 
            if(a[i]%2==1){
                for(int j=1;j<=na;j++) 
                    if(a[j]%2==0){
                        memset(tag,false,sizeof(tag));
                        memset(result,0,sizeof(result));
                        for(int k=1;k<=nb;k++) 
                            if(map[i][k]&&map[j][k]){
                                tag[k]=true;
                                sum++;
                            }
                    }
                    now=0;
                    for(int k=1;k<=tot;k++){
                        memset(use,false,sizeof(use));
                            if(tag[mod[k]]&&dfs(mod[k])) now++;
                    }
                    ans=max(ans,sum-now+2);
            }
        cout<<ans;
    }
    return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值