WC2016 挑战NPC 一般图匹配

博客介绍了WC2016比赛中的一道图匹配问题,涉及n个球和m个筐子,每个筐子最多放3个球,每个球有特定的放置筐子条件。内容解释了如何通过建模将问题转化为图论问题,通过最大匹配算法求解,并给出非二分图的最大匹配的带花树算法链接和问题的解决方案代码。
摘要由CSDN通过智能技术生成

题目:

n 个球,用整数 1 n  编号。还有 m 个筐子,用整数 1 m  编号。

每个筐子最多能装 3 个球。

每个球只能放进特定的筐子中。具体有 e   个条件,第 i  个条件用两个整数 vi ui 描述,表示编号为 vi 的球可以放进编号为 ui 的筐子中。

每个球都必须放进一个筐子中。如果一个筐子内有不超过 1 个球,那么我们称这样的筐子为半空的。

求半空的筐子最多有多少个,以及在最优方案中,每个球分别放在哪个筐子中。


思路:

非常巧妙的建模, 将一个框子拆分成三个槽, 三个槽之间连边, 对于每个条件从球向对应框的三个槽连边, 若最大匹配为 MaxMatch, 则可讨论发现 Ans = n - MaxMatch.

由于不是二分图,只能用带花树算法,传送门:

http://blog.csdn.net/xuezhongfenfei/article/details/10148445


代码:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>

#define For(i,j,k) for(int i = j;i <= (int)k;i++)
#define Forr(i,j,k) for(int i = j;i >= (int)k;i--)
#define Set(i,j) memset(i, j, sizeof(i))
#define pb push_back

const int N = 610;

using namespace std;

struct BlossomTree{
	vector<int> G[N];
	queue<int> q;
	int Next[N], link[N], type[N], fa[N], vis[N];
	int n;

	int find(int x){
		return x == fa[x] ? x : fa[x] = find(fa[x]);
	}

	void Add(int u, int v){
		G[u].pb(v), G[v].pb(u);
	}

	void init(int _n){
		n = _n;
		Set(link, 0);
		For(i,1,n) G[i].clear();
	}

	void Combine(int x, int p){
		while(x != p){
			int u = link[x], v = Next[u];
			if(find(v) != p) Next[v] = u;
			if(type[u] == 1) type[u] = 2, q.push(u);
			fa[find(x)] = find(u), fa[find(u)] = find(v);
			x = v;
		}
	}

	void Contract(int x, int y){
		Set(vis, 0);
		int LCA = 0;
		for(int i = x;i;i = Next[link[i]]) i = find(i), vis[i] = 1;
		for(int i = y;i;i = Next[link[i]]) {i = find(i); if(vis[i]) {LCA = i; break;}}
		if(find(x) != LCA) Next[x] = y;
		if(find(y) != LCA) Next[y] = x;
		Combine(x, LCA);
		Combine(y, LCA);
	}

	void BFS(int S){
		Set(type, 0), Set(Next, 0);
		For(i,1,n) fa[i] = i;
		while(!q.empty()) q.pop();
		q.push(S), type[S] = 2;
		while(!q.empty()){
			int x = q.front(); q.pop();
			For(i,0,G[x].size() - 1){
				int y = G[x][i];
				if(find(x) == find(y) || link[x] == y || type[y] == 1) continue;
				if(type[y] == 2) Contract(x, y);
				else if(link[y]){
					Next[y] = x;
					type[y] = 1;
					type[link[y]] = 2;
					q.push(link[y]);
				}
				else{
					Next[y] = x;
					int pos = y, u = Next[pos], v = link[u];
					while(pos){
						link[u] = pos, link[pos] = u;
						pos = v;
						u = Next[pos], v = link[u];
					}
					return;
				}
			}
		}
	}

	int MaxMatch(){
		Forr(i,n,1) if(!link[i]) BFS(i);
		int ret = 0;
		For(i,1,n) if(link[i]) ++ret;
		return ret / 2;
	}
}Tree;

int main(){
	int T, n, m, e;
	scanf("%d", &T);
	while(T--){
		scanf("%d%d%d", &n, &m, &e);
		Tree.init(n + 3 * m);
		For(i,0,m-1) Tree.Add(3 * i + 1, 3 * i + 2);
		while(e--){
			int x, y;
			scanf("%d%d", &x, &y);
			--y;
			For(i,1,3) Tree.Add(3 * m + x, y * 3 + i);
		}
		printf("%d\n", Tree.MaxMatch() - n);
		For(i,1,n) printf("%d%c", (Tree.link[3 * m + i] - 1) / 3 + 1, i == n ? '\n' : ' ');
	}
	return 0;
}



评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值