[SMOJ220]太空飞行计划

来源:互联网 发布:老版本的知乎 ios 编辑:程序博客网 时间:2024/05/17 04:53

跟之前做的几道二分图匹配不同,从这题开始,我们要试着将问题转化为最小割,再最终转化为求最大流。

根据题意,净收益=所选实验得到的总资助-配置所选实验仪器的总花费。也可以转化为净收益=可以得到的总资助-不选实验失去的资助-配置所选实验仪器的总花费。显然,可以得到的总资助是一定的。要使净收益最大,就要使不选实验失去的资助+所选实验仪器的总花费尽可能小。

建模流程:

  1. 构造一个图,顶点有每个实验和实验仪器 以及一个源点 s 和汇点 t
  2. s 出发,向每个实验 Ei 引出一条容量为 Pi 的有向边
  3. 从每个仪器 Ij 出发,向 t 引出一条容量为 Ck 的有向边
  4. 每个实验分别向所需的仪器引出一条容量为无穷大的有向边

然后求图的最大流,最大流即需要花费的钱。为什么这样子就可以了求出需要花费的钱?因为对于这样的网来说,图上有一点从 s 流到 t ,就说明有 1 点资金需要花费,而如果从 s 引出的边全部满流就说明,这实验把所有的资金全部花费了。如果从 s 引出的边中某一条边没有满流,就说明做这个实验是可以赚钱的(因为这个实验的资金没有全部花费)。

或者,如果把一条 s 连接实验的边删去,代表不做这个实验,就会相应失去一定的资助;如果把仪器连接 t 的边删去,代表这个仪器,同样会消耗一定的花费。当 st 不连通时,s 所能到达的点是所选的实验及其仪器,不能到达的点是不选的。为了使 st 不连通所割去的边的总容量,就是损失的钱。
根据一开始所说的,需要使失去的钱尽量少,即求最小割,于是就可以转化为最大流求解。

参考代码:

//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;}


原创粉丝点击