太空飞行计划问题(最大流)

202 篇文章 0 订阅
123 篇文章 0 订阅
Description


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

Input

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

Output

每组输出最佳实验方案的净收益

Sample Input

2 3
10 2 1 2
25 2 2 3
5 6 7

Sample Output

17

题目出自nefu476

题意中文就不描述了。

思路:

最大权闭合图,转化为求最小割,最终为最大流。

最大利润 = 总收入 — 最大权闭合图权值 即

最大利润 = 总收入 — 最大流

明确了考察对象之后,注意到关键的就是图的建立;

首先要增加一个源点,一个汇点,然后源点向实验各引一条边,权值为实验经费,各器材向汇点引一条边,权值为支出,中间的实验与对应器材之间连一条边,权值为maxlongint ,图就建立成功。

接下来就直接用最大流算法求出最大流,用总收入减去最大流即可。

 然后要求输出方案,事实上就是找割的S集和T集,只要从源点开始搜索,只要残留网络中还有剩余容量的边都可以通过,这样访问到的点就是S集中的点。

为什么这样可以求出割呢?因为最小割中的边必定满流,它的逆否命题:不满流的边必定不是最小割的边也一定成立,因此不满流的边连接的两个点一定在同一个集之中,证毕。


#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include<string.h>
#include<algorithm>
#include<math.h>
#include<queue>
#include<stack>
using namespace std;
typedef long long ll;
const   int oo=1e9;/**oo 表示无穷大*/
const  int mm=111111111;/**mm 表示边的最大数量,记住要是原图的两倍,在加边的时候都是双向的*/
const  int mn=2010;/**mn 表示点的最大数量*/
int node,src,dest,edge;/**node 表示节点数,src 表示源点,dest 表示汇点,edge 统计边数*/
int ver[mm],flow[mm],nex[mm];
int head[mn],work[mn],dis[mn],q[mn];
void prepare(int _node, int _src,int _dest)
{
    node=_node,src=_src,dest=_dest;
    for(int i=0; i<=node; ++i)head[i]=-1;
    edge=0;
}
void addedge( int u,  int v,  int c)
{
    ver[edge]=v,flow[edge]=c,nex[edge]=head[u],head[u]=edge++;
    ver[edge]=u,flow[edge]=0,nex[edge]=head[v],head[v]=edge++;
}
bool Dinic_bfs()
{
    int i,u,v,l,r=0;
    for(i=0; i<node; ++i)dis[i]=-1;
    dis[q[r++]=src]=0;
    for(l=0; l<r; ++l)
        for(i=head[u=q[l]]; i>=0; i=nex[i])
            if(flow[i]&&dis[v=ver[i]]<0)
            {
                dis[q[r++]=v]=dis[u]+1;
                if(v==dest)  return 1;
            }
    return 0;
}
int Dinic_dfs(  int u, int exp)
{
    if(u==dest)  return exp;
    for(  int &i=work[u],v,tmp; i>=0; i=nex[i])
        if(flow[i]&&dis[v=ver[i]]==dis[u]+1&&(tmp=Dinic_dfs(v,min(exp,flow[i])))>0)
        {
            flow[i]-=tmp;
            flow[i^1]+=tmp;
            return tmp;
        }
    return 0;
}
int Dinic_flow()
{
    int i,ret=0,delta;
    while(Dinic_bfs())
    {
        for(i=0; i<node; ++i)work[i]=head[i];
        while((delta=Dinic_dfs(src,oo)))ret+=delta;
    }
    return ret;
}
int num[1005],nun[1005],a[1005][1005];
int main()
{
    int n,m;
    while(~scanf("%d%d",&m,&n))
    {
        int sum=0;
        for(int i=1; i<=m; i++)
        {
            scanf("%d%d",&num[i],&a[i][0]);
            sum+=num[i];
            for(int j=1; j<=a[i][0]; j++)
                scanf("%d",&a[i][j]);
        }
        for(int i=1; i<=n; i++)
            scanf("%d",&nun[i]);
        prepare(n+m+2,0,n+m+1);
        for(int i=1; i<=m; i++)
        {
            addedge(0,i,num[i]);
            for(int j=1; j<=a[i][0]; j++)
                addedge(i,a[i][j]+m,nun[a[i][j]]);
        }
        for(int i=m+1; i<=m+n; i++)
            addedge(i,n+m+1,nun[i-m]);
        printf("%d\n",sum-Dinic_flow());
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值