1. 程式人生 > >帶花樹演算法 UOJ#79. 一般圖最大匹配

帶花樹演算法 UOJ#79. 一般圖最大匹配

帶花樹演算法

匈牙利演算法可以解決二分圖匹配的問題,但是因為二分圖有一個特殊的性質,那就是不會出現一個有奇數點的環。然而對於每一個有偶數點的環,在環裡面無論怎麼匹配都是可以的,然而假如有奇數的環就不一樣了。

不想寫廢話了。。

————————————正文——————————

時間複雜度大概是O(n^2)

首先,思路肯定是找增廣路,也是讓每一個沒匹配的點去嘗試進行匹配,看是否可以有新的匹配。

首先,在沒出現奇環之前,我們把他當做一個普通的二分圖來看,是沒毛病的。

於是有了id這一個陣列,id有三種不同的狀態

-1:沒訪問過  0:S點  1:T

首先我們假設一開始要增廣的點是一個S

由於寫的是廣搜,所以我們需要一個數組,

pre,表示,假設在這次匹配中,第i個點的配偶根別人跑了,他該和誰配對。

然後對於找到方案的這一段程式碼就很簡單了。。

 

那麼接下來的問題是如何來進行尋找增廣路的操作了

假設現在我們在佇列中待處理的點為x

首先,假如我們遍歷到一個以前沒有訪問過的點y,那麼我們就讓他的配偶繼續進行增廣。

Q:為什麼是讓他的配偶去呢?

A:因為我們現在是要假設x要與y相連啊,拿肯定是看看y的配偶是否可以換人啦,這一步感覺和普通的匈牙利是沒什麼太大的不同的

 

然後我們可以發現一個問題啊,那就是我們每一個待擴充套件的點x都是一個S點,嗯,都是S點。這就說明每一個點都是由S點擴展出來的。

那麼除了上面的情況外,剩下的就是環了

假設我們出現的是一個偶環,那麼就直接無視——至於為什麼,大概想一下就可以了

假如我們發現的是一個奇環,那就有大大的不一樣了,因為假如我們這個環中有任意一個點可以在外面找到增廣路的話,這個環就變成了一個偶數環,問題就解決了。

 

比如說上面這個圖,X就是我們要增廣的點,假如出現了這樣一個環,那麼只要1,2,3,4隨便一個點可以找到增廣路,那麼X就可以有配偶啦^_^

然後下文我們假設這個環的突然出現時因為出現了2————3這一條邊

鋪墊:我們在處理完一個奇環後,我們是可以把他試做一個點的(因為這個環中只要有一個點成功就行),所以這裡寫了一個並查集,來使點變成一個環,我的並查集下文用的是f,當然,這個大點是一個

S點。

那麼我們怎樣知道他是不是一個奇環呢?

也很簡單,只要我們訪問到的點也是S點就是了。這個自己想一下應該也可以想出來

那麼剩下的操作就是如何讓他們去遍歷了。

首先我們要知道這個環長什麼樣吧,比如說在上圖中,2,3肯定是由之前的同一個點增廣時所牽扯出來的,也就是有一個點使得他們間接連通現在才回出現環,上圖就是x,所以我們要先知道他是從哪裡來的。於是我們需要一個lca,當然這個lca也寫得比較巧妙~~

 

然後就是處理環了,分兩段進行,

一段就是處理2x這一條路,另一段就是處理3x這一條路,兩個的操作是一樣的,所以只需要寫一個就可以了。

其中一個操作肯定是讓圖中所有的點去增廣,因為一開始的S點肯定都已經入隊增廣了,所以我們要新加入的點就只有之前的T點了。

其次就是維護pre了,這個過程也比較簡單,就是先讓出現新邊的兩條pre互連,然後讓上面的每一個T點與擴充套件他的S點相連。然後假如我們在其中任意有一個點找到增廣路了,根據pre,你就可以得出一條沒有衝突的匹配關係。這個我覺得自己腦補一下就好了。

 

還是舉個栗子吧。。

還是上面的圖,假如你現在T4找到了增廣路,那麼3就會去找2,2的原配偶1就會去找x

假如是S3找到了增廣路,那麼4就會找到x1,2關係不用變,這個和沒環沒啥區別。

完結撒花~~

全程式碼大部分都是照著別人的寫的。。

#include<cstdio>
#include<cstring>
#define swap(x,y) {int tt=x;x=y;y=tt;}
const int N=505*2;
const int M=124750*2;
int f[N];
struct qq
{
	int x,y;
	int last;
}s[M];
int num,last[N];
int n,m;
void init (int x,int y)
{
	num++;
	s[num].x=x;s[num].y=y;
	s[num].last=last[x];
	last[x]=num;
}
int match[N];
int id[N];//這個點是什麼點
//-1:沒訪問過  0:S點  1:T點 
int q[N];//要擴充套件的佇列————也就是我們要嘗試幫誰換配偶 
int pre[N];//在這次過程中,x的新配偶是誰
int Tim,vis[N];//對於lca的標記以及時間軸 
int find (int x)
{
	if (f[x]==x) return f[x];
	f[x]=find(f[x]);
	return f[x];
}
int lca (int x,int y)//尋找lca 
{
	Tim++;
	while (vis[x]!=Tim)
	{
		if (x!=0)
		{
			x=find(x);//先找到花根 
			if (vis[x]==Tim) return x;
			vis[x]=Tim;
			if (match[x]!=0) x=find(pre[match[x]]);
			//因為在之前我們知道,每一個S點的配偶(也就是T點)的pre 都是指向他的父親的,於是就直接這麼跳就可以了
			//還有要注意的是,一定要先去到花根,因為他們現在已經是一個點了,只有花根的pre才指向他們真正的父親 
			else x=0;
		}
		swap(x,y);
	}
	return x;
}
int st,ed;
void change (int x,int y,int k)//環  出現的是x---y的連邊  已知根是k 
{
	while (find(x)!=k)
	{
		pre[x]=y;
		int z=match[x];
		id[z]=0;q[ed++]=z;if (ed>=N-1) ed=1;
		if (find(z)==z) f[z]=k;
		if (find(x)==x) f[x]=k;
		y=z;x=pre[y];
	}
}
void check (int X)//儘量幫助x尋找增廣路 
{
	for (int u=1;u<=n;u++) {f[u]=u;id[u]=-1;}
	st=1;ed=2;
	q[st]=X;id[X]=0;
	while (st!=ed)
	{
		int x=q[st];
		for (int u=last[x];u!=-1;u=s[u].last)
		{
			int y=s[u].y;
			if (match[y]==0&&y!=X)
			//當然match[X]=0,但X(這次來尋找配偶的點)並不是一個可行的東西,所以不能算可行解 
			{
				pre[y]=x;//先假設他與x相連
				int last,t,now=y;
				while (now!=0)//當然,這次來的X的match是為0,要是能更新到0就是結束 
				{
					t=pre[now];//now新的配偶
					last=match[t];//理所當然啦 
					match[t]=now;match[now]=t;
					now=last;
				}
				return ;
			}
			if (id[y]==-1)//找到一個沒有訪問過的點————進行擴充套件
			{
				id[y]=1;
				pre[y]=x;//先假設他與x相連
				id[match[y]]=0;q[ed++]=match[y];
				if (ed>=N-1) ed=1;
			}
			else if (id[y]==0&&find(x)!=find(y))//出現一個以前未處理過的奇環
			{
				int g=lca(x,y);
				change(x,y,g);change(y,x,g);
			}
		}
		st++;
		if (st>=N-1) st=1;
	}
}
int main()
{
	memset(vis,0,sizeof(vis));Tim=0;
	memset(match,0,sizeof(match));
	num=0;memset(last,-1,sizeof(last));
	scanf("%d%d",&n,&m);
	for (int u=1;u<=m;u++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		init(x,y);init(y,x);
	}
	for (int u=1;u<=n;u++) 
		if (match[u]==0) 
			check(u);
	int ans=0;
	for (int u=1;u<=n;u++)
		if (match[u]!=0) ans++;
	printf("%d\n",ans/2);
	for (int u=1;u<=n;u++) printf("%d ",match[u]);
	return 0;
}

肯定寫錯了很多東西。。。。。。