bzoj2744[HEOI2012]朋友圈

题目链接:bzoj2744

题目大意:

两个国家看成是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是朋友

求出最大朋友圈的人数


题解:

匈牙利求二分图的最大匹配

%%%[迷の想到tarjan的我ORZ...

这个题的意思是要我们求一个图的最大团。嗯。一定有特殊性质才使这道题可做。

首先观察A国人,a xor b mod 2=1,就是说当且仅当这两人一奇一偶的时候才为朋友,就是说A国的相当于一个二分图。而二分图的最大团只有2。

然后看B国人,可以发现,奇数间是个完全图,偶数间也是(在先不考虑第二个条件的情况下)。那么它的补图就是个二分图,考虑埋第二个条件也是。而在某图是个二分图的前提下,其最大独立子集就等于它补图的最大团。于是我们构图的时候就直接构造它的补图,其实就是把每对奇偶都连上..(额不要忘了去掉满足第二个条件的)。然后跑匈牙利就好了。

所以做法就是,枚举A国选多少人(0,1,2),哪些人。根据选出来的A国人选出能与所有被选到的A国人成为朋友的B国人,构图(如上所述的那样↑),上匈牙利。因为有最大独立子集=总点数-最大匹配,算出来后加上A国的人数就好了。


..我觉得我的代码还是算好懂的吧,用了时间戳。嗯。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
#define maxn 250
#define maxm 3100

int A[maxn],B[maxm];
int len,lem,bf[maxm];
int ask[maxm],tim;//用时间戳
int ln[maxm],lm[maxm];
bool bk[maxm][maxm],bo[maxn][maxm];
//bk[i][j]存B国中i,j是否突破了奇偶限制而成为了朋友
int mymax(int x,int y){return (x>y)?x:y;}
bool ffind(int x)
{
	int i;
	for (i=1;i<=lem;i++)
	 if (ask[i]!=tim && !bk[ln[x]][lm[i]])
	//如果成为了朋友 那么补图中他们两个是不能连边的
	 {
		 ask[i]=tim;
		 if (bf[i]==-1 || ffind(bf[i]))
		 {
			 bf[i]=x;
			 return true;
		 }
	 }
	return false;
}
bool count(int x)
{
	int ret=0;
	while (x)
	{
		if (x&1) ret++;
		x>>=1;
	}return ret&1;
}
int main()
{
	//freopen("a.in","r",stdin);
	//freopen("a.out","w",stdout);
	int n,m,r,i,j,k,x,y,ia,num,ans;
	scanf("%d%d%d",&n,&m,&r);
	for (i=1;i<=n;i++)
	 scanf("%d",&A[i]);
	for (i=1;i<=m;i++)
	 scanf("%d",&B[i]);
	memset(bo,false,sizeof(bo));
	memset(bk,false,sizeof(bk));
	for (i=1;i<=r;i++)
	{
		scanf("%d%d",&x,&y);
		bo[x][y]=true;
	}
	for (i=1;i<m;i++)
	 for (j=i+1;j<=m;j++)
	  if ((B[i]+B[j])&1)
	  {
		  if (count(B[i]|B[j])) bk[i][j]=bk[j][i]=true;
	  }
	memset(ask,0,sizeof(ask));
	ans=tim=0;
	
	len=lem=num=0;
	for (i=1;i<=m;i++)
		if (B[i]&1) ln[++len]=i;
		else lm[++lem]=i;
	memset(bf,-1,sizeof(bf));
	for (i=1;i<=len;i++)
	{
		tim++;
		if (ffind(i)) num++;
	}
	ans=mymax(ans,len+lem-num);
	
	for (i=1;i<=n;i++)
	{
		len=lem=num=0;
		for (j=1;j<=m;j++)
		 if (bo[i][j])
		 {
			 if (B[j]&1) ln[++len]=j;
			 else lm[++lem]=j;
		 }
		memset(bf,-1,sizeof(bf));
		for (j=1;j<=len;j++)
		{
			tim++;
			if (ffind(j)) num++;
		}
		ans=mymax(ans,1+len+lem-num);
	}

	for (i=1;i<n;i++)
		for (j=i+1;j<=n;j++) if ((A[i]+A[j])&1)
		{
			len=lem=num=0;
			for (k=1;k<=m;k++)
			 if (bo[i][k] && bo[j][k])
			 {
				 if (B[k]&1) ln[++len]=k;
				 else lm[++lem]=k;
			 }
			memset(bf,-1,sizeof(bf));
			for (k=1;k<=len;k++)
			{
				tim++;
				if (ffind(k)) num++;
			}
			ans=mymax(ans,2+len+lem-num);
		}
	printf("%d\n",ans);
	return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值