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

题目描述:
在很久很久以前,曾经有两个国家和睦相处,无忧无虑的生活着。一年一度的评比大会开始了,作为和平的两国,一个朋友圈数量最多的永远都是最值得他人的尊敬,所以现在就是需要你求朋友圈的最大数目。
两个国家看成是AB两国,现在是两个国家的描述:

  1. A国:每个人都有一个友善值,当两个A国人的友善值a、b,如果a xor b mod 2=1,
    那么这两个人都是朋友,否则不是;
  2. B国:每个人都有一个友善值,当两个B国人的友善值a、b,如果a xor b mod 2=0
    或者 (a or b)化成二进制有奇数个1,那么两个人是朋友,否则不是朋友;
  3. A、B两国之间的人也有可能是朋友,数据中将会给出A、B之间“朋友”的情况。
  4. 在AB两国,朋友圈的定义:一个朋友圈集合S,满足S∈A∪ B,对于所有的i,j∈ S ,i 和 j 是朋友

由于落后的古代,没有电脑这个也就成了每年最大的难题,而你能帮他们求出最大朋 友圈的人数吗?

题目分析:
求图的最大团。
最大团等于补图的最大独立集。

由于这个问题是nphard问题,所以我们要观察这个图的特殊性。
对于A集合:
奇数和偶数之间有边,所以A集合中最多选两个。

对于B集合:
奇数和奇数之间都有边,偶数和偶数之间都有边,奇数和偶数之间可能右边。
那么对于这B集合的补图,就一定是,奇数之间相互无边,偶数之间相互无边,奇数和偶数构成二分图。

二分图最大独立集等于二分图总点数减去最大匹配数。

所以对于A集合我们枚举选择哪些点(不选,选一个,选一奇一偶)
然后对于B集合中不为A集合中选中点朋友的点,删去。
对剩下的B集合的点跑二分图最大匹配,找出最大团即可。

能用时间戳的地方就不要用memset,省时
(输入中说多组数据然而并没有,好迷啊=。=)
代码如下:

#include <cstdio>
#include <iostream>
#define N 3100
using namespace std;
inline int Max(int x,int y) { return x>y?x:y; }
int A,B,AB,ans=0;
int a[N],b[N];
int odd[N],even[N],cnto,cnte;
int match[N],tim[N];
int fir[N],nes[N*N],v[N*N],tot=1;
int ban[N],vis[N];
bool e[N][N];
int T1,T2;
void edge(int x,int y)
{
    v[++tot]=y;
    nes[tot]=fir[x];
    fir[x]=tot;
}
bool Count(int x,int y)
{
    int tmp=x^y,cnt=0;
    if((tmp&1)==0) return false;
    tmp=x|y;
    for(;tmp;tmp-=tmp&-tmp) cnt++;
    return !(cnt&1);

}
bool Hungary(int c)
{
    for(int t=fir[c];t;t=nes[t])
    {
        if(ban[v[t]]==T1 || vis[v[t]]==T2) continue;
        vis[v[t]]=T2;
        if(tim[v[t]]!=T1 || Hungary(match[v[t]]))
        {
            match[v[t]]=c;
            match[c]=v[t];
            tim[v[t]]=tim[c]=T1;
            return true;
        }
    }
    return false;
}
int calc(int x=0,int y=0)
{
    int ans=B;
    T1++;
    for(int i=1;i<=B;i++)
        if((x && !e[x][i]) || (y && !e[y][i]))
            ban[i]=T1,ans--;
    for(int i=1;i<=B;i++)
        if(tim[i]!=T1 && ban[i]!=T1)
        {
            T2++;
            if(Hungary(i)) ans--;
        }
    return ans;
}
int main()
{
    scanf("%d%d%d",&A,&B,&AB);
    for(int i=1;i<=A;i++) scanf("%d",&a[i]);
    for(int i=1;i<=B;i++) scanf("%d",&b[i]);
    for(int i=1;i<=A;i++)
    {
        if(a[i]&1) odd[++cnto]=i;
        else even[++cnte]=i;
    }
    for(int i=1;i<=B;i++)
        for(int j=1;j<=B;j++)
            if(Count(b[i],b[j])) edge(i,j);
    for(int i=1,x,y;i<=AB;i++)
    {
        scanf("%d%d",&x,&y);
        e[x][y]=true;
    }
    ans=calc();
    for(int i=1;i<=A;i++) ans=Max(calc(i)+1,ans);
    for(int i=1;i<=cnto;i++)
        for(int j=1;j<=cnte;j++)
            ans=Max(calc(odd[i],even[j])+2,ans);
    printf("%d\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值