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

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
#include
#include
#include
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++;
    }

你可能感兴趣的:(&,图论,题解,二分图,匈牙利算法,时间戳)