圖的匹配問題相關模板

基本二分圖匹配匈牙利算法和一些基本的定理不再闡述
必經\可行點,必經\可行點
必經可行邊在這裏可以看到。
可行點:同可行邊,所有可行邊連接的點,都是可行點,否則不是可行點。
必經點:除暴力枚舉外,我們在跑完網絡流的殘留圖上枚舉所有的未匹配點,進行dfs,記錄所有歷經點,所有沒有被歷經的點,都是必經點。(因爲每個點每條邊最多經歷一邊,所以dfs複雜度是O(n+m) )
二分圖最大邊權匹配
費用流
二分圖多重匹配
最大流
最大積匹配
我們想找到最大權匹配,而這個最大最大權是指最大的邊權之積。我們可以取自然對數,然後跑最大權匹配。但是注意精度問題。
一般圖最大匹配(帶花樹)
用於一般圖的最大匹配。
【經典模型】:棋盤博弈,兩個人互相從棋盤取棋子,每個人取得棋子只能是上一個人取棋子的曼哈頓距離小於L的棋子。誰沒法取了就輸了。第一個棋子隨便取。我們將所有距離小於L的棋子連邊建圖。最後如果有一個聯通塊不是完備匹配,先手就贏了。先手可以取沒法必備的那個點,然後每次取先手的匹配點即可。

/*
帶花樹, 求一般圖的最大匹配:

我們思考求二分圖的匈牙利算法爲啥不適合一般圖:一般圖有
奇環, 在匈牙利算法尋找交錯路增廣的時候,所有尋找到未匹
配的終點時, 都可以變換交錯路的邊,實現增廣但是, 在一般
圖中, 我們尋找到一個終點,因爲交錯路中存在的奇環, 我們
不能保證這次增廣的正確性,(在紙上畫畫就可知)所以我們處
理奇環情況: 
在一個大小爲(2k+1)的奇環中, 最多匹配k對頂點, 另一個必
須連接外面, 所以我們考慮將奇環所成一個點, 具體做法就
是利用並查集的合併.

注意事項: 複雜度O(n^3) 點的編號必須從一開始, 否則初始
化pre[] = -1. 

*/

//洛谷 P6113
const int N = 1e3+5;
const int M = 1e5+5;
int n, m, pre[N], tim = 0;
queue<int> q;
int he[N], ne[M], ver[M], tot;
int match[N], fa[N], tp[N], tic[N];
int fi(int x)
{ // 並查集尋根
	if (fa[x] == x) return x;
	return fa[x] = fi(fa[x]);
}
inline void add(int x, int y)
{
	ver[++tot] = y;
	ne[tot] = he[x];
	he[x] = tot;
}
int lca(int x, int y)
{ // 找lca
	tim++;
	for (; ; swap(x, y)) if (x)  // 點的編號要從1開始
	{ // 暴力跳祖先, x, y依次跳躍以減小跳躍次數
		x = fi(x);
		if (tic[x] == tim) return x;
		tic[x] = tim, x = pre[match[x]];
	}
}
void mer (int x, int y, int _lca)
{
	while(fi(x) != _lca)
	{ // 合併 ( 縮點 / [ 開  花 ] )
		pre[x] = y; y = match[x];
		if (tp[y] == 2) tp[y] = 1; q.push(y);
		if (fi(x) == x) fa[x] = _lca;
		if (fi(y) == y) fa[y] = _lca;
		x = pre[y];
	}
}
int flowerdance (int s)
{ 
	for (int i = 1; i <= n; i++) fa[i] = i; 
	memset(tp, 0, sizeof(tp)); memset(pre, 0, sizeof(pre));
	while(q.size()) q.pop();
	tp[s] = 1; q.push(s);
	while(q.size())
	{ // 從s點開始bfs, 一次尋找增廣路的操作.
		int x = q.front(); q.pop();
		for (int i = he[x]; i; i = ne[i])
		{
			int y = ver[i];
			if (fi(y) == fi(x) || tp[y] == 2) continue;
            // 如果y x已經在一朵花裏, 或者y已經被搜過, 且形成的是偶環, 直接無視
			if (!tp[y])
			{ // y沒有被搜過.
				tp[y] = 2; pre[y] = x;
                // y染色爲2, y入點爲x
				if (!match[y])
				{ // 如果y沒有被匹配過, 增廣路成功, 回溯記錄每一對match
					int la, te;
					for (int now = y; now; now = la)
					{
						la = match[te = pre[now]];
						match[now] = te; match[te] = now;
					}
					return 1;
				}
				tp[match[y]] = 1; q.push(match[y]);
                // 如果y被匹配過, 把y染色1, 並丟到隊列, 繼續搜索. 
			}
			else if (tp[y] == 1)
			{ // y的顏色爲1, 出現奇環
				int l = lca(x, y); // 找lca
				mer(x, y, l);
				mer(y, x, l); // 開花
			}
		}
	}
	return 0;
}
int main()
{
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++)
	{
		int x, y; scanf("%d%d", &x, &y);
		add(x, y); add(y, x);
	}
	int ans = 0;
	for (int i = 1; i <= n; i++) ans += (!match[i] && flowerdance(i));
	printf("%d\n", ans);
	for (int i = 1; i <= n; i++) printf("%d ", match[i]);
	return 0;
}

一般圖的最大、最小權完全匹配
注意這裏是完全匹配

/*
一般圖最大\最小權, 完全匹配
節點編號必須從0開始. 
如果要求最大匹配, 邊權爲正. 否則爲負. 

*/


// poj 2404
const int N = 128;
const int inf = 0x3f3f3f3f;
int n, m;
int deg[N], e[N][N];
int cnt; int g[N][N]; 
int dis[N]; bool vis[N];
int rec[N], tot, M[N];
bool Dij(int x)
{
	rec[tot++] = x;
	if (vis[x]) return 1;
	vis[x] = 1;
	for (int y = 0; y < cnt; y++)
	{
		if (y != x && M[x] != y && !vis[y])
		{
			int w = M[y];
			if (dis[w] < dis[x] + g[x][y] - g[y][w])
			{
				dis[w] = dis[x] + g[x][y] - g[y][w];
				if (Dij(w)) return 1;
			}
		}
	}
	tot--;
	vis[x] = 0;
	return 0;
}
int match()
{
	for (int i = 0; i < cnt; i+=2) M[i] = i+1, M[i+1] = i;
	int _cnt = 0;
	while(1)
	{
		tot = 0;
		bool flag = 0;
		memset(dis, 0, sizeof(dis));
		memset(vis, 0, sizeof(vis));
		for (int i = 0; i < cnt; i++)
		{
			if (Dij(i))
			{
				flag = 1;
				int nx = M[rec[tot-1]];
				int j;
				for (j = tot - 2; rec[j] != rec[tot-1]; j--)
				{
					M[nx] = rec[j];
					swap(nx, M[rec[j]]);
				}
				M[nx] = rec[j];
				M[rec[j]] = nx;
				break;
			}
		}
		if (!flag)
		{
			_cnt++;
			if (_cnt >= 3) break;
		}
	}
	int sum = 0;
	for (int i = 0; i < cnt; i++)
	{
		int y = M[i];
		if (i < y) sum += g[i][y];
	}
	return sum;
}
void init()
{
	memset(deg, 0, sizeof(deg));
	for (int i = 0; i < n; i++)
		for (int j = 0; j < n; j++) e[i][j] = inf;
}
void floyd ()
{
	for (int k = 0; k < n; k++)
		for (int i = 0; i < n; i++)
			for (int j = 0; j < n; j++)
				e[i][j] = min(e[i][j], e[i][k] + e[k][j]);
}
int main()
{
	while(scanf("%d", &n), n)
	{
		init();
		scanf("%d", &m);
		int sum = 0;
		while(m--)
		{
			int x, y, w; scanf("%d%d%d", &x, &y, &w);
			x--; y--;
			e[x][y] = e[y][x] = min(e[x][y], w);
			sum += w;
			deg[x]++; deg[y]++;
		}
		int V[N], ct = 0;
		for (int i = 0; i < n; i++) if (deg[i] & 1) V[ct++] = i;
		floyd();
		cnt = ct;
		for (int i = 0; i < ct; i++)
			for (int j = 0; j < ct; j++)
				g[i][j] = -e[V[i]][V[j]]; //求最小權就壓負得, 求最大權就壓正的
		printf("%d\n", sum - match());
	}
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章