洛谷P2762 [网络流24题]太空飞行计划

Address


Solution

  • 题目描述比较含糊……
  • 首先要明确的是:购置一个仪器后可以使用多次,因此可以存在做某一个实验亏损,但和其它实验一起做反而能节省费用的情况。
  • 先说下建图:
    • 由源点向每个实验连边,边权为实验获得的报酬。
    • 由每个实验向所需的仪器连边,边权为正无穷。
    • 由每个仪器向汇点连边,边权为购置仪器的花费。
  • 则 最优收益 = 所有实验的报酬总和 - 该图的最大流 。
  • 容易知道:
    • 若某个实验亏损,从该实验流入汇点的流量等于实验获得的报酬,减去后即相当于不做这个实验。
    • 若某个实验有收益,从该实验流入汇点的流量等于仪器购置的费用,减去后即相当于计算收益。
  • 本题很麻烦的一个点是输入,更麻烦的一个点是输出方案……
  • 简单判断仪器到汇点满流显然是不正确的,因为若某个实验肯定亏损(即无法和其它实验一起做来节省费用),它到所需要的一些仪器的边满流,另一些仪器的边不满流,这时满流的那些仪器是不能购置的。
  • 考虑每次删去一个仪器到汇点满流的边,再跑一次最大流,若原来的最大流和这时的差值等于边权,则这个仪器是必须购置的。
  • 同样容易知道:
    • 若某个实验肯定亏损,原来就无法满流,删去满流边后流量流入了其它的边,因此差值改变。
    • 若某个实验有收益,原来就能满流,删去满流边后仍然满流,因此差值不改变。
  • 最后再由仪器的购置来判断实验是否要做。

Code

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cctype>

using namespace std;

namespace inout
{
    const int S = 1 << 20;
    char frd[S], *ihed = frd + S;
    const char *ital = ihed;

    inline char inChar()
    {
        if (ihed == ital)
            fread(frd, 1, S, stdin), ihed = frd;
        return *ihed++;
    }

    inline bool get(int &res)
    {
        char ch; res = 0; bool flag = false;
        while (!isdigit(ch = inChar()) && ch != '-')
            if (ch == '\n') return false;
        (ch == '-' ? flag = true : res = ch ^ 48);
        while (isdigit(ch = inChar()))
            res = res * 10 + ch - 48;
        return ch == '\n' ? false : true; 
    }   
};
using namespace inout;

const int Maxn = 0x3f3f3f3f;
const int N = 205, M = 10005;
int lst[N], nxt[M], to[M], cst[M], cur[N], que[N], lev[N];
int n, m, src, des, qr, sum, Ans, T = 1, a[N], b[N][N]; 
bool cut[M], stp[N];

inline void Link(int x, int y, int z)
{
    nxt[++T] = lst[x]; lst[x] = T; to[T] = y; cst[T] = z;
    nxt[++T] = lst[y]; lst[y] = T; to[T] = x; cst[T] = 0;
}

inline bool Bfs()
{ 
    for (int i = 1; i <= des; ++i) cur[i] = lst[i], lev[i] = -1;
    que[qr = 1] = src; lev[src] = 0; int x, y;
    for (int j = 1; j <= qr; ++j)
        for (int i = lst[x = que[j]]; i; i = nxt[i])
        if (!cut[i] && cst[i] > 0 && lev[y = to[i]] == -1)
        {
            lev[y] = lev[x] + 1; que[++qr] = y;
            if (y == des) return true;
        }
    return false;
}

inline int Min(int x, int y) {return x < y ? x : y;}
inline int Dinic(int x, int flow)
{
    if (x == des) return flow;
    int y, res = 0, Del;
    for (int &i = cur[x]; i; i = nxt[i])
        if (!cut[i] && cst[i] > 0 && lev[y = to[i]] > lev[x])
        {
            Del = Dinic(y, Min(flow - res, cst[i]));
            if (Del)
            {
                cst[i] -= Del; cst[i ^ 1] += Del;
                res += Del; if (res == flow) break;
            } 
        }
    if (res != flow) lev[x] = -1;
    return res;
}

int main()
{
    get(m); get(n); int x;
    src = n + m + 1; des = src + 1;
    for (int i = 1; i <= m; ++i)
    {
        get(x); Link(src, i, x); Ans += x;
        while (get(x)) Link(i, (b[i][++b[i][0]] = x) + m, Maxn);
        if (x) Link(i, (b[i][++b[i][0]] = x) + m, Maxn);
    }

    for (int i = 1; i <= n; ++i)
        get(x), Link(i + m, des, a[i + m] = x);

    while (Bfs()) sum += Dinic(src, Maxn);
    for (int i = lst[des]; i; i = nxt[i])
    if (cst[i] == a[to[i]])
    {
        cut[i] = cut[i ^ 1] = true;
        int res = 0, w = cst[i];
        for (int j = 1; j <= des; ++j)
            for (int k = lst[j]; k; k = nxt[k])
                if (!cut[k] && (j < to[k] || j == src)) 
                    cst[k] += cst[k ^ 1], cst[k ^ 1] = 0;
        while (Bfs()) res += Dinic(src, Maxn);
        if (sum - res == cst[i]) stp[to[i] - m] = true;
        cut[i] = cut[i ^ 1] = false;
    }

    for (int i = 1; i <= m; ++i)    
    {
        bool flag = false;
        for (int j = 1; j <= b[i][0]; ++j)
            if (!stp[b[i][j]])
            {
                flag = true;
                break;
            }
        if (!flag) printf("%d ", i);
    }
    putchar('\n');
    for (int i = 1; i <= n; ++i)
        if (stp[i]) printf("%d ", i);
    printf("\n%d\n", Ans - sum);
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值