bzoj2744 [HEOI2012]朋友圈 ( 二分图最大团转补图最大独立集+时间戳优化+匈牙利算法)

来源:互联网 发布:c 编程游戏写法 编辑:程序博客网 时间:2024/06/07 17:35

bzoj2744 [HEOI2012]朋友圈

原题地址:http://www.lydsy.com/JudgeOnline/problem.php?id=2744

题意:
求朋友圈的最大数目。
两个国家的描述:
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 是朋友。

求朋友圈的最大数目。
数据范围
两类数据
第一类:|A|<=200 |B| <= 200
第二类:|A| <= 10 |B| <= 3000

题解:
求朋友圈即求图中的最大团。

首先,对于A国,可以知道只有奇数和偶数可以有边,因此最多就是两个人一组。

对于B国,若求B的最大团,由题目中给出的朋友方式可知:B图的补图是二分图(按奇偶分),因为 二分图最大团 = 其补图的最大独立集,又因为 二分图最大独立集 =顶点数 - 最小顶点覆盖 并且 二分图最小顶点覆盖 = 最大匹配数 ,故可以用二分图匹配求解。

我们计算 :
1.不选国的。
2.选A国的一人(枚举A国每一个人)
3.选A国的两个人(枚举A国的每两个人)
这三种情况时,答案是B国 只包含与A国选点都是朋友的点 的图 的最大团分别加上0,1,2,取最大值即可。

每次重新建图显然不优,只需要每次时间戳++,把满足要求的点打上标记,跑匈牙利时只跑这些点即可,注意点数-匹配数时也要减去这些点。

然后一般的匈牙利每次memset掉vis数组也慢,于是又采用时间戳来优化。

(时间戳优化很棒)

代码:

#include<cstdio>#include<iostream>#include<algorithm>#include<cstring>using namespace std;const int N=3010;int A,B,m,g[N][N],head[N],to[N*N>>2],nxt[N*N>>2],num=0,a[N],b[N],ans;int match[N],tim[N],tag[N],vis[N],inc=0,t=0;bool f[N][N];bool check(int x){    int cnt=0;    for(;x;x>>=1) if(x&1)cnt++;    return ( (cnt%2)==1);}bool hungary(int x){    for(int i=head[x];i;i=nxt[i])    {        int v=to[i];        if((tag[v]==inc)&&(vis[v]!=t))         {            vis[v]=t;            if(!match[v]||(tim[v]!=inc+1)||hungary(match[v]))            {                match[v]=x;                tim[v]=inc+1;                return 1;            }        }    }    return 0;}int solve(){    int ret=0;    for(int i=1;i<=B;i++)    if(tag[i]!=inc) ret++;    for(int i=1;i<=B;i++)    if(tag[i]==inc)    {        t++;        if(hungary(i)) ret++;    }       return B-ret;}void build(int u,int v){    num++;    to[num]=v;    nxt[num]=head[u];    head[u]=num;}int main(){    scanf("%d%d%d",&A,&B,&m);    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<=B;i++)    if(b[i]&1)    for(int j=1;j<=B;j++)    if((!(b[j]&1))&&!check(b[i]|b[j])) build(i,j); //建立B的补图     for(int i=1;i<=m;i++)    {        int x,y;        scanf("%d%d",&x,&y);        f[x][y]=1;    }       inc=t=ans=0;    ans=max(ans,solve());    for(int i=1;i<=A;i++)    {        inc++;        for(int j=1;j<=B;j++)        if(f[i][j]) tag[j]=inc;        ans=max(ans,solve()+1);    }       for(int i=1;i<=A;i++)    if(a[i]&1)    {        for(int j=1;j<=A;j++)        if(!(a[j]&1))        {            inc++;            for(int k=1;k<=B;k++)            if(f[i][k]&&f[j][k]) tag[k]=inc;            ans=max(ans,solve()+2);        }    }       printf("%d\n",ans);    return 0;}

我本意是想打一道二分图来学习匈牙利,谁料完全不记得二分图性质了,在此复习:

二分图的最大匹配数=最小点覆盖数
二分图的独立数=顶点数-最小点覆盖数=顶点数-最大匹配数
二分图最小边覆盖=图中点的个数-最大匹配数=最大独立集。
二分图最大团 = 其补图的最大独立集
DAG的最小路径覆盖=原图的结点数-新图的最大匹配数(每个点拆点后作最大匹配)

然后记录下匈牙利:

bool hungary(int x){    for(int i=head[x];i;i=nxt[i])    {        int v=to[i];        if(!vis[v])        {            vis[v]=1;            if(!match[v]||hungary(match[v]))            {                match[v]=x;                return 1;            }        }    }       return 0;}
    for(int i=1;i<=n;i++)    {        memset(vis,0,sizeof(vis));        if(hungary(i)) cnt++;    }
阅读全文
0 0