跟之前做的几道二分图匹配不同,从这题开始,我们要试着将问题转化为最小割,再最终转化为求最大流。
根据题意,净收益=所选实验得到的总资助-配置所选实验仪器的总花费。也可以转化为净收益=可以得到的总资助-不选实验失去的资助-配置所选实验仪器的总花费。显然,可以得到的总资助是一定的。要使净收益最大,就要使不选实验失去的资助+所选实验仪器的总花费尽可能小。
建模流程:
- 构造一个图,顶点有每个实验和实验仪器 以及一个源点
s
和汇点
t - 从
s
出发,向每个实验
Ei 引出一条容量为 Pi 的有向边 - 从每个仪器
Ij
出发,向
t
引出一条容量为
Ck 的有向边 - 每个实验分别向所需的仪器引出一条容量为无穷大的有向边
然后求图的最大流,最大流即需要花费的钱。为什么这样子就可以了求出需要花费的钱?因为对于这样的网来说,图上有一点从
s
流到
或者,如果把一条
s
连接实验的边删去,代表不做这个实验,就会相应失去一定的资助;如果把仪器连接
根据一开始所说的,需要使失去的钱尽量少,即求最小割,于是就可以转化为最大流求解。
参考代码:
//prog82
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <sstream>
using namespace std;
const int MAXM = 500;
const int MAXN = 500;
const int INF = 0x3f3f3f3f;
struct Edge {
Edge *next;
int cap;
int dest;
} edges[MAXM * MAXN], *current, *first_edge[MAXM + MAXN];
int m, n, s, t;
bool vis[MAXM + MAXN];
Edge *counterpart(Edge *x) {
return edges + ((x - edges) ^ 1);
}
void insert(int u, int v, int c) {
current -> next = first_edge[u];
current -> cap = c;
current -> dest = v;
first_edge[u] = current ++;
}
int dfs(int u, int f) {
if (u == t) return f;
if (vis[u]) return 0; else vis[u] = true;
for (Edge *p = first_edge[u]; p; p = p -> next)
if (p -> cap)
if (int res = dfs(p -> dest, min(f, p -> cap))) {
p -> cap -= res;
counterpart(p) -> cap += res;
return res;
}
return 0;
}
int main(void) { string tmp;
// ios::sync_with_stdio(false);
freopen("2209.in", "r", stdin);
freopen("2209.out", "w", stdout);
cin >> m >> n; /*cin.get();*/ getline(cin, tmp); current = edges; //注意读掉换行符最好不要用 cin.get(),这个会因系统不同而出偏差,用 getline 代替
s = 0; t = m + n + 1; int ans = 0;
fill(first_edge, first_edge + t + 1, (Edge*)0);
for (int i = 1; i <= m; i++) {
getline(cin, tmp);// cout << tmp << endl;
stringstream ss(tmp);
int money; ss >> money; ans += money;
insert(s, i, money); insert(i, s, 0);
int equipment; //不定量的数据输入比较麻烦,用 sstream 虽然慢了些但是方便处理
while (ss >> equipment) insert(i, equipment + m, INF), insert(equipment + m, i, 0);
}
for (int i = 1; i <= n; i++) {
int cost; cin >> cost;// cout << cost << endl;
insert(i + m, t, cost); insert(t, i + m, 0);
}
while (true) {
memset(vis, false, sizeof vis);
if (int res = dfs(s, INF)) ans -= res; else break;
}
//这里用到了一个技巧:最后一次增广所能到达的点就是最小割中的 S 集合,即被选的实验和仪器
for (int i = 1; i <= m; i++) if (vis[i]) cout << i << ' ';
cout << endl;
for (int i = 1; i <= n; i++) if (vis[i + m]) cout << i << ' ';
cout << endl << ans << endl;
return 0;
}