您的位置:首页 > 其它

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

2017-10-10 22:12 447 查看

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

,head
,to[N*N>>2],nxt[N*N>>2],num=0,a
,b
,ans;
int match
,tim
,tag
,vis
,inc=0,t=0;
bool f

;
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++;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: