【BZOJ4405】【WC2016】挑战NPC(带花树)

【BZOJ4405】【WC2016】挑战NPC(带花树)

题面

BZOJ
洛谷
Uoj

Description

小N最近在研究NP完全问题,小O看小N研究得热火朝天,便给他出了一道这样的题目:
有n个球,用整数1到n编号。还有m个筐子,用整数1到m编号。
每个筐子最多能装3个球。
每个球只能放进特定的筐子中。具体有e个条件,第i个条件用两个整数vi和ui描述,表示编号为vi的球可以放进编号为ui的筐子中。
每个球都必须放进一个筐子中。如果一个筐子内有不超过1个球,那么我们称这样的筐子为半空的。
求半空的筐子最多有多少个,以及在最优方案中,每个球分别放在哪个筐子中。
小N看到题目后瞬间没了思路,站在旁边看热闹的小I嘿嘿一笑:“水题!”
然后三言两语道出了一个多项式算法。
小N瞬间就惊呆了,三秒钟后他回过神来一拍桌子:
“不对!这个问题显然是NP完全问题,你算法肯定有错!”
小I浅笑:“所以,等我领图灵奖吧!”
小O只会出题不会做题,所以找到了你——请你对这个问题进行探究,并写一个程序解决此题。

Input

第一行包含1个正整数T,表示有T组数据。
对于每组数据,第一行包含3个正整数n,m,e,表示球的个数,筐子的个数和条件的个数。
接下来e行,每行包含2个整数vi,ui,表示编号为vi的球可以放进编号为ui的筐子。

Output

对于每组数据,先输出一行,包含一个整数,表示半空的筐子最多有多少个。

Sample Input

1

4 3 6

1 1

2 1

2 2

3 2

3 3

4 3

Sample Output

2

HINT

对于所有数据,T≤5,1≤n≤3m。保证 1≤vi≤n,1≤ui≤m,且不会出现重复的条件。

保证至少有一种合法方案,使得每个球都放进了筐子,且每个筐子内球的个数不超过 3。

M<=100

题解

考虑一下可以放的球数和对答案的贡献:
3个球:1
2个球:1
1个球:0
0个球:0
我们发现就是可以放的球数整除\(2\)的结果
所以,把一个篮子拆成三个
一个球还是一个球
如果一个球可以放进一个篮子里,证明着球可以与篮子拆分出来的任意一个点匹配
现在要求的相当于篮子自身能够匹配的最大数目
如果超过了两个空,那么就可以自身与自身匹配了,从而产生贡献一。

所以考虑如下构图:
每个篮子是三个点,点与点之间互相连边
每个球是一个点,可以向它可以放的篮子拆出来的三个点连边。

这样的话,因为保证所有球都有匹配,
所以最大匹配数=球数+有贡献的篮子自身的匹配

所以最后的答案就是最大匹配数-球数。

至于如何计算方案,一定优秀增广球,再增广篮子,否则会出现篮子自身优先匹配,然后球没有匹配的情况,到时方案错误(虽然答案也是对的。。。)

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define RG register
#define MAX 1111
inline int read()
{
    RG int x=0,t=1;RG char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return x*t;
}
struct Line{int v,next;}e[MAX*MAX];
int h[MAX],cnt=1;
inline void Add(int u,int v){e[cnt]=(Line){v,h[u]};h[u]=cnt++;}
queue<int> Q;
int dfn[MAX],tim;
int match[MAX],pre[MAX];
int f[MAX],vis[MAX],n,m,E;
int getf(int x){return x==f[x]?x:f[x]=getf(f[x]);}
int lca(int u,int v)
{
	++tim;u=getf(u);v=getf(v);
	while(dfn[u]!=tim)
	{
		dfn[u]=tim;
		u=getf(pre[match[u]]);
		if(v)swap(u,v);
	}
	return u;
}
void Blossom(int x,int y,int w)
{
	while(getf(x)!=w)
	{
		pre[x]=y,y=match[x];
		if(vis[y]==2)vis[y]=1,Q.push(y);
		if(getf(x)==x)f[x]=w;
		if(getf(y)==y)f[y]=w;
		x=pre[y];
	}
}
bool Aug(int S)
{
	for(int i=1;i<=n+m+m+m;++i)f[i]=i,vis[i]=pre[i]=0;
	while(!Q.empty())Q.pop();Q.push(S);vis[S]=1;
	while(!Q.empty())
	{
		int u=Q.front();Q.pop();
		for(int i=h[u];i;i=e[i].next)
		{
			int v=e[i].v;
			if(getf(u)==getf(v)||vis[v]==2)continue;
			if(!vis[v])
			{
				vis[v]=2;pre[v]=u;
				if(!match[v])
				{
					for(int x=v,lst;x;x=lst)
						lst=match[pre[x]],match[x]=pre[x],match[pre[x]]=x;
					return true;
				}
				vis[match[v]]=1,Q.push(match[v]);
			}
			else
			{
				int w=lca(u,v);
				Blossom(u,v,w);
				Blossom(v,u,w);
			}
		}
	}
	return false;
}
void init()
{
	memset(h,0,sizeof(h));cnt=1;
	memset(dfn,0,sizeof(dfn));tim=0;
	memset(match,0,sizeof(match));
}
int main()
{
	int T=read();
	while(T--)
	{
		n=read();m=read();E=read();
		init();int ans=0;
		for(int i=1;i<=m;++i)
		{
			Add(i,i+m);Add(i+m,i);
			Add(i,i+m+m);Add(i+m+m,i);
			Add(i+m,i+m+m);Add(i+m+m,i+m);
		}
		for(int i=1;i<=E;++i)
		{
			int v=read(),u=read();
			Add(v+m+m+m,u);Add(u,v+m+m+m);
			Add(v+m+m+m,u+m);Add(u+m,v+m+m+m);
			Add(v+m+m+m,u+m+m);Add(u+m+m,v+m+m+m);
		}
		for(int i=m+m+m+n;i;--i)if(!match[i]&&Aug(i))++ans;
		printf("%d\n",ans-n);
		for(int i=1;i<=n;++i)printf("%d ",(match[i+m+m+m]-1)%m+1);puts("");
	}
	return 0;
}

posted @ 2018-04-04 21:49  小蒟蒻yyb  阅读(250)  评论(0编辑  收藏  举报