洛谷 P2762 太空飞行计划问题(自己理解的口胡版)

题目描述
W 教授正在为国家航天中心计划一系列的太空飞行。每次太空飞行可进行一系列商业性实验而获取利润。现已确定了一个可供选择的实验集合E={E1,E2,…,Em},和进行这些实验需要使用的全部仪器的集合I={I1,I2,…In}。实验Ej需要用到的仪器是I的子集RjÍI。配置仪器Ik的费用为ck美元。实验Ej的赞助商已同意为该实验结果支付pj美元。W教授的任务是找出一个有效算法,确定在一次太空飞行中要进行哪些实验并因此而配置哪些仪器才能使太空飞行的净收益最大。这里净收益是指进行实验所获得的全部收入与配置仪器的全部费用的差额。

对于给定的实验和仪器配置情况,编程找出净收益最大的试验计划。

输入格式
第1行有2 个正整数m和n。m是实验数,n是仪器数。接下来的m 行,每行是一个实验的有关数据。第一个数赞助商同意支付该实验的费用;接着是该实验需要用到的若干仪器的编号。最后一行的n个数是配置每个仪器的费用。

输出格式
第1 行是实验编号;第2行是仪器编号;最后一行是净收益。

输入输出样例
输入 #1 复制
2 3
10 1 2
25 2 3
5 6 7
输出 #1 复制
1 2
1 2 3
17

题意: 有m个实验n个仪器,每个实验都需要数个仪器。同时,每做一个实验会得到ai报酬,每个仪器也有着他们各自的价格bi。对于已经购买到的仪器,可以重复使用。问,最大的净利润为多少?
思路: 这道题是最大权闭合图问题,但是看了很久的证明愣是没有看懂,这里我就分享一下自己对这道题的理解吧~
根据最大权闭合图的解释,我们将权值为正的节点与S相连,权值为负的点与T相连,边为他们对应权值的绝对值,节点之间的权值为INF。
一. 若此实验的报酬不合理(即,当前实验报酬 < 仪器花费和),通过对最小割的理解,我们对于当前实验的最小割为实验的报酬(即,将实验节点与S的边割掉)
二. 若此实验的报酬合理(即,当前实验的报酬 >= 仪器花费和),那么我们对于当前实验的最小割为仪器花费和(即,将仪器与T的边割掉)
三. 若不同实验之前的仪器存在交叉的情况,若存在某一项实验合理,对于当前实验,通过最小割将存在交叉仪器与T之间的边割掉,减轻了其他实验的开销。再对其他实验进行最小割,即可得到答案。

不含证明,纯属口胡,若有错误,还望指正

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2100;
const int INF = (1<<30);

struct Edge
{
    int v,w,next;
}e[MAXN];
int head[MAXN],cnt;
int dep[MAXN],cur[MAXN];
int n,m,S,T;

int min(int x,int y){return x > y ? y : x;}
int max(int x,int y){return x > y ? x : y;}

bool flag;
int read()
{
    char ch=' ';int res=0;
    while(!isdigit(ch)) ch=getchar();
    while(isdigit(ch)) res=res*10+ch-'0',ch=getchar();
    if(ch=='\n') flag=1;
    return res;
}

void addedge(int u, int v, int w)
{
    e[cnt].next = head[u];
    e[cnt].v = v;
    e[cnt].w = w;
    head[u] = cnt++;
}

void init()
{
    for(int i = 0; i <= n+m+1; ++i) head[i] = -1;
    cnt = 0;
    S = 0;  T = n+m+1;
}

int bfs()
{
    for(int i = 0; i <= n+m+1; ++i) dep[i] = INF;
    queue<int>q;
    q.push(S);  dep[S] = 0;
    while(!q.empty()){
        int u = q.front();  q.pop();
        for(int i = head[u]; ~i; i = e[i].next){
            int v = e[i].v, w = e[i].w;
            if(w > 0 && dep[v] > dep[u] + 1){
                dep[v] = dep[u] + 1;
                q.push(v);
            }
        }
    }
    return dep[T] != INF;
}

int dfs(int s,int mw)
{
    if(!mw || s == T)   return mw;
    int flow = 0;
    for(int &i = cur[s]; ~i; i = e[i].next){
        int v = e[i].v, w = e[i].w;
        if(w <= 0 || dep[v] != dep[s] + 1)  continue;
        int cw = dfs(v,min(w,mw));
        if(cw <= 0) continue;
        flow += cw;
        mw -= cw;
        e[i].w -= cw;
        e[i^1].w += cw;
        if(!mw) break;
    }
    return flow;
}

int dinic()
{
    int ret = 0;
    while(bfs()){
        for(int i = 0; i <= T; ++i)
            cur[i] = head[i];
        ret += dfs(S,INF);
    }
    return ret;
}

int main()
{
    n = read(), m = read();
    init();
    int sum = 0;
    for(int i = 1; i <= n; ++i){
        int x = read(); sum += x;
        addedge(S,i,x); addedge(i,S,0);
        flag = 0;
        while(!flag){
            x = read();
            addedge(i,n+x,INF);
            addedge(n+x,i,0);
        }
    }
    for(int i = 1; i <= m; ++i){
        int x = read();
        addedge(i+n,T,x);
        addedge(T,i+n,0);
    }
    int ret = dinic();
    for(int i = 1; i <= n; ++i)
        if(dep[i] != INF)  cout<<i<<" ";
    cout<<endl;
    for(int i = 1; i <= m; ++i)
        if(dep[i+n] != INF)  cout<<i<<" ";
    cout<<endl;
    cout<<sum - ret<<endl;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值