Loj 6001「网络流 24 题」太空飞行计划

(http://www.elijahqi.win/2017/12/15/loj-6001%E3%80%8C%E7%BD%91%E7%BB%9C%E6%B5%81-24-%E9%A2%98%E3%80%8D%E5%A4%AA%E7%A9%BA%E9%A3%9E%E8%A1%8C%E8%AE%A1%E5%88%92/)
题目背景
题目描述

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

说明

感谢@zhouyonglong 提供spj

n,m<=50

问题 要求我们求出最大的收益

参考了一些blog的证明 觉得似乎明白的一些 首先增设源点为S 汇点为T

那么 我们知道S所连接的点构成一个图N T构成一个图M

那么假设我们这个简单割的容量为C 那么 我们很容易知道C=x1+y1即 M中正权+N中负权的绝对值

设N这个图的闭合图的权值为W

W= N中正权 -N中负权的绝对值 那么将W+C等同于所有图中正权的和 而且那么我们要求的W的最大值就是TOT-C 那么TOT是一个定值 说明我们割的容量需要最小才好

学习内容来自这篇blog:https://www.cnblogs.com/wuyiqi/archive/2012/03/12/2391960.html

以下内容参考 胡伯涛 《最小割模型在信息学竞赛中的应用》,感谢他为我们提供这么优秀的论文。

看不懂以上论文的同学,可以试试看一下以下内容,本文无大量的数学符号,方便阅读理解。

首先我们由一道题来引入,见 [线性规划与网络流24题 2] 太空飞行计划问题 。

这道题中,实验依赖于仪器,而实验和仪器都有权值,且仪器为负,实验为正。

这里闭合图的概念就很好引出了。在一个图中,我们选取一些点构成集合,记为V,且集合中的出边(即集合中的点的向外连出的弧),所指向的终点(弧头)也在V中,则我们称V为闭合图。最大权闭合图即在所有闭合图中,集合中点的权值之和最大的V,我们称V为最大权闭合图。

上图中闭合图有

{5}、{2,5}、{4,5}

{2,4,5}、{3,4,5}

{1,2,3,4,5}、{1,2,4,5}

最大权闭合图为{3,4,5}。

针对本题而言,我们将实验与仪器间连一条有向边,实验为起点(弧尾),仪器为终点(弧头)。则如果我们选择一个闭合图,那么这个闭合图中包含的实验所需要的仪器也最这个闭合图里。而最大权闭合图即为题目的解。

了解了最大权闭合图的概念,接下来我们就需要知道如何求最大权闭合图。

首先我们将其转化为一个网络(现在不要问为什么,接下来会证明用网络可以求解)。构造一个源点S,汇点T。我们将S与所有权值为正的点连一条容量为其权值的边,将所有权值为负的点与T连一条容量为其权值的绝对值的边,原来的边将其容量定为正无穷。

上图即被转化为如左图网络。

首先引入结论,最小割所产生的两个集合中,其源点S所在集合(除去S)为最大权闭合图,接下来我们来说明一些结论。

证明:最小割为简单割。

引入一下简单割的概念:割集的每条边都与S或T关联。(请下面阅读时一定分清最小割与简单割,容易混淆)

那么为什么最小割是简单割呢?因为除S和T之外的点间的边的容量是正无穷,最小割的容量不可能为正无穷。所以,得证。

证明网络中的简单割与原图中闭合图存在一一对应的关系。(即所有闭合图都是简单割,简单割也必定是一个闭合图)。

证明闭合图是简单割:如果闭合图不是简单割(反证法)。那么说明有一条边是容量为正无穷的边,则说明闭合图中有一条出边的终点不在闭合图中,矛盾。

证明简单割是闭合图:因为简单割不含正无穷的边,所以不含有连向另一个集合(除T)的点,所以其出边的终点都在简单割中,满足闭合图定义。得正。

证明最小割所产生的两个集合中,其源点S所在集合(除去S)为最大权闭合图。

首先我们记一个简单割的容量为C,且S所在集合为N,T所在集合为M。

则C=M中所有权值为正的点的权值(即S与M中点相连的边的容量)+N中所有权值为负的点权值的绝对值(即N中点与T中点相连边的容量)。记(C=x1+y1);(很好理解,不理解画一个图或想象一下就明白了)。

我们记N这个闭合图的权值和为W。

则W=N中权值为正的点的权值-N中权值为负的点的权值的绝对值。记(W=x2-y2);

则W+C=x1+y1+x2-y2。

因为明显y1=y2,所以W+C=x1+x2;

x1为M中所有权值为正的点的权值,x2为N中权值为正的点的权值。

所以x1+x2=所有权值为正的点的权值之和(记为TOT).

所以我们得到W+C=TOT.整理一下W=TOT-C.

到这里我们就得到了闭合图的权值与简单割的容量的关系。

因为TOT为定值,所以我们欲使W最大,即C最小,即此时这个简单割为最小割,此时闭合图为其源点S所在集合(除去S)。得正。

至此,我们就将最大权闭合图问题转化为了求最小割的问题。求最小割用最小割容量=最大流,即可将问题转化为求最大流的问题。

#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define inf 0x3f3f3f3f
#define N 110
using namespace std;
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
    while(ch<='9'&&ch>='0'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
struct node{
    int y,z,next;
}data[N*N];
int num=1,h[N],T,level[N],n,m,sum;
inline bool bfs(){
    memset(level,0,sizeof(level));level[0]=1;queue<int>q;q.push(0);
    while(!q.empty()){
        int x=q.front();q.pop();
        for (int i=h[x];i;i=data[i].next){
            int y=data[i].y,z=data[i].z;
            if (level[y]||!z) continue;level[y]=level[x]+1;q.push(y);if (y==T) return 1;
        }
    }return 0;
}
inline int dfs(int x,int s){
    if (x==T) return s;int ss=s;
    for (int i=h[x];i;i=data[i].next){
        int y=data[i].y,z=data[i].z;
        if(level[y]==level[x]+1&&z){
            int xx=dfs(y,min(s,z));if (!xx) level[y]=0;
            s-=xx;data[i].z-=xx;data[i^1].z+=xx;if (!s) return ss;
        }
    }return ss-s;
}
int main(){
    freopen("loj.in","r",stdin);
    m=read();n=read();T=n+m+1;
    for (int i=1;i<=m;++i){
        int x;scanf("%d",&x);sum+=x;
        data[++num].y=i;data[num].next=h[0];h[0]=num;data[num].z=x;
        data[++num].y=0;data[num].next=h[i];h[i]=num;data[num].z=0;
        char ch;
        while((ch=getchar())!='\r'&&ch!='\n'){
            scanf("%d",&x);data[++num].y=m+x;data[num].z=inf;data[num].next=h[i];h[i]=num;
            data[++num].y=i;data[num].z=0;data[num].next=h[x+m];h[x+m]=num;
        }
    }
    for (int i=1;i<=n;++i){
        int x;scanf("%d",&x);
        data[++num].y=T;data[num].next=h[i+m];data[num].z=x;h[i+m]=num;
        data[++num].y=i+m;data[num].next=h[T];data[num].z=0;h[T]=num; 
    }int ans=0;
    while(bfs()) ans+=dfs(0,inf);
    for (int i=1;i<=m;++i) if (level[i]) printf("%d ",i);printf("\n");
    for (int i=m+1;i<=m+n;++i) if(level[i]) printf("%d ",i-m);printf("\n");
    printf("%d",sum-ans);
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值