P2762 太空飞行计划问题(网络流最小割)

太空飞行计划问题

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

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

输入格式:

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

输出格式:

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

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

解:

这道题实际上是一个模板。
网上说叫什么最大权闭合图。
具体的是什么意思呢?
我们把实验连向源点,器材连向汇点。边权是它们的代价或者是回报。相关联的点,我们把它们之间连一条正无穷的边。我们割掉一条边表示损失这么多钱。也就是说我们割掉实验,代表不做这个实验,割掉器材代表买下这个器材。对于一个实验来说,我们要么不做(割掉自己),要么把它连接的所有器材都买下来(割掉它连向的所有器材)。也就是说,我们一个合法解一定是源汇不连通。同时我们如果要达到最小损失,这个最优解就对应最小割。
因为这里记录的是损失,所以我们先把答案加上所有回报,再减去我们网络流的结果。

这里总结一下这一类问题,因为最近看到两道相同类型的。
对于两个子集 A A B,分成二分图。如果得到 Bi B i ,我们要花费{ G|A G | A },我们就相互连边。最小割即答案。比较难想的就是我割 A A 表示用A,而割 B B 表示不用B

这一道题还有一个地方就是要输出方案。把在残量网络所有和源点联通的点输出即可。

code(读入好毒):

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
struct lxy{
    int to,flow,next;
}eg[6005];

int n,m,head[105],cnt=-1,s,t,x,ans;
int layer[105],vis[105];

void add(int op,int ed,int flow){
    eg[++cnt].next=head[op];
    eg[cnt].flow=flow;
    eg[cnt].to=ed;
    head[op]=cnt;
}

bool bfs(){
    memset(layer,0,sizeof(layer));
    queue <int> d;
    d.push(s);layer[s]=1;
    while(!d.empty()){
        int now=d.front();d.pop();
        for(int i=head[now];i!=-1;i=eg[i].next){
            if(eg[i].flow!=0&&layer[eg[i].to]==0){
                layer[eg[i].to]=1+layer[now];
                d.push(eg[i].to);
            }
        }
    }
    return layer[t];
}

int dfs(int u,int a){
    if(u==t||a==0) return a;
    int f,flow=0;
    for(int i=head[u];i!=-1;i=eg[i].next){
        if(eg[i].flow!=0&&layer[eg[i].to]==layer[u]+1){
            f=dfs(eg[i].to,min(a,eg[i].flow));
            flow+=f;a-=f;
            eg[i].flow-=f;eg[i^1].flow+=f;
            if(a==0) break;
        }
    }
    return flow;
}

int dinic(){
    int ret=0;
    while(bfs()){
        ret+=dfs(s,0x3f3f3f3f);
    }
    return ret;
}

void colder(int u)
{
    vis[u]=1;
    for(int i=head[u];i!=-1;i=eg[i].next)
      if(eg[i].flow!=0&&vis[eg[i].to]==0)
        colder(eg[i].to);
}

int main()
{
    memset(head,-1,sizeof(head));
    scanf("%d%d",&m,&n);
    s=0,t=m+n+1;
    for(int i=1;i<=m;i++){
        scanf("%d",&x);
        add(s,i,x),add(i,s,0);
        ans+=x;
        char tools[10000];
        memset(tools,0,sizeof tools);
        cin.getline(tools,10000);
        int ulen=0,tool;
        while(sscanf(tools+ulen,"%d",&tool)==1){
            add(i,tool+m,0x3f3f3f3f);
            add(tool+m,i,0);
            if(tool==0) ulen++;
            else{
              while(tool)
                tool/=10,ulen++;
            }
            ulen++;
        }
    }
    for(int i=1;i<=n;i++){
        scanf("%d",&x);
        add(i+m,t,x),add(t,i+m,0);
    }
    ans-=dinic();
    colder(s);
    for(int i=1;i<=m;i++)
      if(vis[i]==1)
        printf("%d ",i);
    printf("\n");
    for(int i=m+1;i<=m+n;i++)
      if(vis[i]==1)
        printf("%d ",i-m);
    printf("\n");
    printf("%d",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值