POJ3281-Dining 最大流解决匹配问题

在这里插入图片描述
如果只是分配食物的话,那么用二分图最大匹配就能够解决了。但遇到这种需要同时给一头牛分配所喜欢的食物和饮料的情况,就不能很好的处理了。不过,我们可以将食物和饮料所对应的两个匹配通过下面的方法联合起来求解。

  • 图的顶点在食物对应的匹配中的食物和牛,饮料对应的匹配中的饮料和牛之外,还有一个源点 s s s和一个汇点 t t t
  • 在两个匹配相同的牛之间连一条边,在 s s s和所有食物, t t t和所有饮料之间连一条边
  • 边的方向为 s s s-食物—牛—牛—饮料— t t t,容量全都为1。

在这里插入图片描述
接下来我们解释为什么要这样建立这个图,比较容易想到的是,将食物、牛、饮料放在一个图里,再加上 s s s t t t,也就是说上图中只有一行牛,此时跑一遍最大流,但是实际上这个算法是错误的

比如下面这组数据

1 3 3
3 3 1 2 3 1 2 3

答案应该是1,但当前的算法输出是3,算法的错误是中间的牛被重复利用了。于是我们想到了一个技巧,拆点。
把一个牛拆成两个点,中间连接一条流量为1的边,这样保证了一头牛最多只会被利用一次。
于是重新构图:
建一个超级源点连房间,食物连牛1,牛1连牛2,牛2连饮料,饮料连一个新建的超级汇点。
注:其中牛1和牛2实际上是指同一头牛。
然后接着跑一遍网络流求出答案即可。

#include<iostream>
#include<cmath>
#include<string.h>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<iomanip>
using namespace std;

#define ll long long
#define vec vector<ll>
#define arr vector<vec>
#define inf 19270817
#define Max 1000

struct edge {
	ll to, cap, rev;//重点,容量,反向边
	edge(ll a, ll b, ll c) { to = a, cap = b, rev = c; }
};
vector<edge> G[100010];
bool used[100010];
ll food[Max][Max], drink[Max][Max], N, F, D;

void addEdge(ll from, ll to, ll cap) {
	G[from].push_back(edge(to, cap, G[to].size()));
	G[to].push_back(edge(from, 0, G[from].size() - 1));//反向的记着设置为0
}

ll dfs(ll v, ll t, ll f) {//深度优先搜索,搜到一条增广路径就撤
	if (v == t) return f;//v一直存储着
	used[v] = true;
	for (unsigned i = 0; i < G[v].size(); i++) {
		edge & e = G[v][i]; ll to = e.to;
		if (!used[to] && e.cap > 0) {
			ll d = dfs(to, t, min(f, e.cap));//从路径后面返回来的最大流
			if (d > 0) {//d>0不能忘了判断,否则经常返回0,就无法探测该节点的其他路径
				e.cap -= d;
				G[e.to][e.rev].cap += d;//v到e.to,e.to又存储着到v的反向路径,记着更新
				return d;
			}
		}
	}
	return 0;
}

//从s到t的最大流
ll maxFlow(ll s, ll t) {
	ll flow = 0;
	while (1) {
		memset(used, 0, sizeof(used));
		ll f = dfs(s, t, inf);
		if (f == 0) return flow;
		flow += f;
	}
}

void solve() {
	// 0~N-1: 食物一侧的牛
	// N~2N-1: 饮料一侧的牛
	// 2N~2N+F-1: 食物
	// 2N+F~2N+F+D-1: 饮料
	int s = 2 * N + F + D, t = s + 1;
	
	//在s与食物之间连边
	for (ll i = 0; i < F; i++)  addEdge(s, 2 * N + i, 1);
	//在t与饮料之间连边
	for (ll i = 0; i < D; i++) addEdge(2 * N + F + i, t, 1);

	for (ll i = 0; i < N; i++) {
		//将食物一侧的牛和饮料一侧的牛进行连边
		addEdge(i, N + i, 1);

		//将牛和所喜欢的食物或者饮料进行连边
		for (ll j = 0; j < F; j++) if (food[i][j]) addEdge(j + 2 * N, i, 1);
		for (ll j = 0; j < D; j++)if (drink[i][j]) addEdge(N + i, 2 * N + F + j, 1);
	}
	cout << maxFlow(s, t) << endl;
}
int main() {
	cin >> N >> F >> D;
	for (ll i = 0; i < N; i++) {
		ll a, b, c; cin >> a >> b;
		while (a--) { cin >> c; food[i][c - 1] = 1; }
		while (b--) { cin >> c; drink[i][c - 1] = 1; }
	}
	solve();
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值