[SMOJ220]太空飞行计划

97 篇文章 0 订阅
10 篇文章 0 订阅

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

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

建模流程:

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

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

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

参考代码:

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


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值