如果只是分配食物的话,那么用二分图最大匹配就能够解决了。但遇到这种需要同时给一头牛分配所喜欢的食物和饮料的情况,就不能很好的处理了。不过,我们可以将食物和饮料所对应的两个匹配通过下面的方法联合起来求解。
- 图的顶点在食物对应的匹配中的食物和牛,饮料对应的匹配中的饮料和牛之外,还有一个源点 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();
}