图的匹配问题相关模板

基本二分图匹配匈牙利算法和一些基本的定理不再阐述
必经\可行点,必经\可行点
必经可行边在这里可以看到。
可行点:同可行边,所有可行边连接的点,都是可行点,否则不是可行点。
必经点:除暴力枚举外,我们在跑完网络流的残留图上枚举所有的未匹配点,进行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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章