2184: 任意图的匹配 - #include <Seter> - using namespace Orz;

2184: 任意图的匹配

Seter posted @ 2011年9月11日 20:13 in BZOJ with tags BlossomTree Match , 2170 阅读

http://www.zybbs.org/JudgeOnline/problem.php?id=2184

RunID User Problem Result Memory Time Language Code Length Submit Time
150752 Seter 2184 Accepted 824 kb 40 ms C/Edit 1957 B 2011-09-11 18:06:56

【第一次写那么长的题解啊囧……】

Obviously……求出给定图的最大匹配ANS后,有ANS*2个人就满足了,剩余N-ANS*2个人还要找人讲话,于是使得班里最吵的最少说话者对数就是ANS+(N-ANS*2)=N-ANS,于是答案就是N-ANS-1。

问题转化为什么求一般图的最大匹配……WQ神神犇给出了一种我无法立杰的神犇算法,果断不会写!像我这种菜鸟只能写写带花树……

好吧我承认,如果你看过某个模板以后会发现这个代码和那个基本一样……没错……背代码党出现了!

一直以为带花树是一种很难懂而且很难写的算法,现在再来看……还是一种很难写的算法,但是还是比较好懂得。

我用的是BFS,DFS也是可以的而且貌似会更快,但是我没找到相关代码……所以……

一开始写了70+行,后来发现好多地方可以改,最后改成50行了……终于比树链剖分短了……但是对于速度还是不很满意啊。


每次将一个点提根,把这个点染成白色。如果图中没有奇环,那么就可以对整张图进行黑白染色(即原图是二分图)。其中白点入队列,黑点有from表示这个黑点是从哪个点来的,白点的from=-1。如果BFS到一个N元奇环,那么:

1.必定是从一个白点U的非匹配边连向另一个白点V组成。

2.这个环上必定已经匹配了(N-1)/2条边(可以自己画一下看看)。

3.由2得,这个环上所有的点的当前匹配边的对面那个点,不在这个环里的只有一个,即只有一条环外匹配边。这么说可能有点混乱,其实就是,把这个环缩成一个大点(环内匹配边全部缩掉)后,这个大点对外也只有一条匹配边,自己画下。


由这三点就可以知道可以缩点了。重点和容易写错的地方就在于怎么缩点!缩点是把整个花朵映射到BASE上,步骤如下:

1.由于是BFS的,所以要先找环中最先visit的点,也即外向匹配的那个白点(DFS也许不用找,是不是DFS就快在这里?还是我哪里写搓了?):这个点就是U和V的LCA。这个LCA暴力查找即可。注意:根节点也算向外匹配。

2.对于U和V分别把U和V所属的BASE的所有点的BASE改成第1步中找到的那个LCA。不过这里的改BASE其实还没改,只是标记了这个点在花朵中(因为如果这个点是一个花朵的话要修改花朵中所有点的BASE)。改BASE很好理解,缩点嘛。但是看代码会发现还要改FROM,这是为什么呢?等等说。(还有,这里能用并查集么?我觉得是不行的,网上有一个代码说是用了并查集,但是速度和这个一样快,而且代码中也没看到findfather……)

3.遍历所有点,若一个点的BASE被标记在花朵中的话,就修改这个点的BASE,并且加入队列。


现在来解释第2步为什么改FROM。为了解决一个点不知道自己应该是黑还是白的问题,我们新建一个白色人工节点,然后把原来的那些点中除了外向匹配的那个白点外的点,都变成黑点,这样他们就都有FROM了。这其中有几个问题:

1.不是新建白色节点么?怎么代码里没有?

答:外向匹配的那个白点(BASE)恰好可以代替这个人工节点,新建节点过于麻烦。

2.环中的其他点不是都变成黑点了么?为什么还加入队列?不是只有白点才入队么?

答:用那个白点代替了人工节点后,其他点的边还没有映射到这个白点上。把那些黑点加入队列只是为了遍历这个人工节点所拥有的边,即这个环所拥有的边。这个操作就相当与把那个白色的人工节点加入队列。

3.环中的黑点FROM怎么定?(注意:无论是原来就是黑点的点还是原来是白点的点,只要不是BASE都可能要改FROM)

这个必须自己画图,否则很难立杰。比如想象戊烷上装了一个乙基:Z...F___A...B___C...D___E...A,Z是根,先找到了F,F找到了A,然后从A开始找了一圈又回到了A。

现在假设我们的B点有一条未匹配边B...S,那么增广路就是S...B___C...D___E...A___F...Z(注意:增广时是按照FROM,MATCH,FROM,MATCH……这样增广的。)。这就可以知道C点的FROM是D,E点的FROM是A。

再假设我们的E点有一条未匹配边E...S,那么增广路变为S...E___D...C___B...A___F...Z,得到D的FROM是C,B的FROM是A。

看出来了罢!对于环中的未匹配边A...B,A的FROM应该改为B,B的FROM应该改为A!当然,BASE的FROM不改:因为BASE还是白点,所以FROM=-1。


从上面的分析可以看出,将人工节点映射到BASE上在空间、时间、编程复杂度上都有大幅度降低(说实话让我加人工节点的话我还真不知道应该怎么写 =。=).

至此所有问题都解决了……开动罢!加油!

#include <stdio.h>
#include <string.h>
#define maxn 1000
#define maxm 3000
int n,m=0,match[maxn],ans=0,base[maxn],from[maxn],que[maxn],bq,eq;
_Bool v[maxn],in[maxn],anc[maxn];
struct edge{struct edge *next;int y;}*map[maxn],st[maxm<<1],*p;
inline getint(){
	int re=0,ch=getchar();
	while(ch<'0'||ch>'9')if((ch=getchar())<=0)return 0;
	for(;ch>='0'&&ch<='9';ch=getchar())re=re*10+ch-48;
	return re;
}inline bfs(x){
	int i,f;
	memset(from,-1,sizeof(from));memset(v,0,sizeof(v));
	for(i=0;i<n;i++)base[i]=i;
	for(v[que[bq=0]=x]=eq=1;bq!=eq;bq++)for(p=map[que[bq]];p;p=p->next)if(base[i=que[bq]]!=base[f=p->y]&&f!=match[i]){
		if(f==x||match[f]!=-1&&from[match[f]]!=-1){
			memset(in,0,sizeof(in));memset(anc,0,sizeof(anc));
			for(i=base[i];(anc[i]=1)&&match[i]!=-1;i=base[from[match[i]]]);
			for(f=base[f];!anc[f];f=base[from[match[f]]]);
			for(i=que[bq];base[i]!=f&&(in[base[i]]=in[base[match[i]]]=1);i=from[i])if(base[from[i=match[i]]]!=f)from[from[i]]=i;
			for(i=p->y;base[i]!=f&&(in[base[i]]=in[base[match[i]]]=1);i=from[i])if(base[from[i=match[i]]]!=f)from[from[i]]=i;
			if(base[que[bq]]!=f)from[que[bq]]=p->y;
			if(base[p->y]!=f)from[p->y]=que[bq];
			for(i=0;i<n;i++)if(in[base[i]]){
				base[i]=f;
				if(!v[i])v[que[eq++]=i]=1;
			}
		}else if(from[f]==-1){
			from[f]=i;
			if(match[f]==-1){
				for(;(i=f)!=-1;match[match[i]=from[i]]=i)f=match[from[i]];
				return 1;
			}else v[que[eq++]=match[p->y]]=1;
		}
	}return 0;
}inline void ADDEDGE(x,y){
	st[m^1].next=map[st[m].y=y];
	st[m].next=map[st[m^1].y=x];
	map[x]=st+m++;map[y]=st+m++;
}main(){
	int i,M;
	while(i=n=getint()){
		memset(match,-1,sizeof(match));memset(map,0,sizeof(map));
		for(ans=m=0,M=getint();M--;ADDEDGE(getint()-1,getint()-1));
		while(i--)if(match[i]==-1)ans+=bfs(i);
		printf("%d\n",n-1-ans);
	}return 0;
}
By Seter

登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter
Host by is-Programmer.com | Power by Chito 1.3.3 beta | © 2007 LinuxGem | Design by Matthew "Agent Spork" McGee