网络流24题2——太空飞行计划问题(最大权闭合图)

网络流24题的第二题,主要是一些学习的总结

努力做到比大佬们的博客都要简单易懂~!适合初学网络流的同学

问题描述:太空飞行通过进行实验获取利润。现已确定了实验集合 E={E_1 ,E_2 …E_m },和进行这些实验需要使用的全部实验仪器的集合 I={I_1 ,I_2 …I_n }。实验 E_j 需要用到的仪器是 I 的子集 R_j ⊆I。配置仪器 I_k 的费用为 c_k 美元。实验 E_j 的赞助商会为该实验结果支付 p_j 美元。对于给定的实验和仪器配置情况,请求出在一次太空飞行中要进行哪些实验并因此而配置哪些仪器,才能使太空飞行的净收益最大。(净收益是指进行实验所获得的全部收入与配置仪器的全部费用的差额)
数据输入:第 1 行有 2 个正整数 m和 n。m 是实验数,n 是仪器数。接下来的 m 行,每行是一个实验的有关数据。第一个数赞助商同意支付该实验的费用;接着是该实验需要用到的若干仪器的编号。最后一行的 n 个数是配置每个仪器的费用。
结果输出:将最佳实验方案输出。第 1 行是实验编号;第 2行是仪器编号;最后一行是净收益。

输入

2 3
10 1 2
25 2 3
5 6 7

输出

1 2
1 2 3
17

这道题网上的一些题解也不是那么直观,主要原因是知识点不了解,所以仅说一下我个人的思路。

一、题目用到的一些概念

首先我们来看一下大佬是怎么分析这道题的:

最大权闭合图问题,可以转化成最小割问题,进而用最大流解决。

不明觉厉!但不管怎么说,对于蒟蒻来讲,做这道题就不用着急,先来了解这两个概念:1. 最小割;2. 最大权闭合图。

1. 最小割

最小割比较简单。在网络流中,最小割可以这么理解:找到一组最小的割边集,让源点S和汇点T之间不连通(割边后S到T的流=0)。而最小割的性质是:最小割的大小等于最大流。所以(对蒟蒻来说)想到最小割直接求最大流dinic求就好了。当然这个性质是可以证明的,大家有兴趣可以看下书和博客,这里不作证明

2. 最大权闭合图

最大权闭合图的定义如下:

在一个图中,我们选取一些点构成集合,记为S,且集合中点连出的边,所指向的终点也在S中,则我们称S闭合图。最大权闭合图即在所有闭合图中,集合中点的权值之和最大S点的权值可能为负),我们称S最大权闭合图

简单来说,闭合图就是在有向图选取一些点集,保证这些点集中每个点指向的点也在点集中。最大权就是这些点的点权之和最大。

而最大权闭合图是可以通过网络流求解的!

二、题目求解

回到这道题本身,选择实验本身有收益,而购买实验仪器有花费。当选择了一个实验,就必须购买这种实验用到的实验仪器。对于这种关系,我们要做的是从实验向其对应的实验仪器连有向边。可以发现,这道题就被我们转化成了求最大权闭合图问题了。

最大权闭合图的求解问题可以通过网络流求解,我们主要从两方面分解这道题目。1. 具体方法是什么;2. 怎么理解这种方法。

1. 具体方法

关于最大权闭合子图和最大密度子图可以看胡伯涛的论文,最大权闭合子图指的是一个有向图G(V,E)的点集V,且该点集满足:如果u在V中,那么u指向的任意后继v也在V中。

给每个点分配一个权值(可正可负),那么最大权闭合图就是一个点权和最大的闭合图。

这一类题的构图方法为:增加一个源点S和一个汇点T,若点i权值w为正,在S到i间添加一条容量为w的边,若点权值为负,在i到T之间加一条容量为-w的边,对于原图的任一条有向边(u,v),添加u到v容量为INF的边。然后对这个网络求最小割,最后的最优解即为总收益(所有点权为正的点的权值和) – 最小割。

简言之,就是新增源点S汇点T,从S向所有正权点边权为正权的有向边,从所有负权点T边权为负权相反数的有向边(注:点权为0的点可以忽略,对结果没有影响),之前有向图中的边权值均初始化为无穷大。

答案为:最大权闭合子图的权 = 所有正权点之和 - 最小割;转化为了求最大流问题。

2. 怎么理解这种方法

首先我们确定的是:最优解 = 总获益 – 未选择点的获益 – 选择点的花费

根据减法的性质:最优解 = 总获益 – ( 未选择点的获益 + 选择点的花费 )

而这个最小割的意义正是括号中的部分,即未选择的点的获益和选择点需要的花费;所以最后的最优解即为 所有正权点之和 ( 总获益 ) – ( 未选择点的获益 + 选择点的花费 ) ( 最小割 ) 。

至于为什么最小割是未选择点的获益和选择点的花费,我们可以这样理解:最小割是让S,T不连通的最小割边集,所以一定不会选择无穷大的边,那就只剩下两类边:S连向的边和连向T的边,也就是连正权点和负权点连出的边。我们不妨分别来想:

① S连向的边,割边选的一定是边权尽可能小的边,即未选择点的收益。

② 连向T的边,割边选的一定是边权尽可能小的边,因为边权是负权点的相反数,所以边权越小=花费越小,即选择点的花费。

三、具体选择的实验和实验仪器

这道题如果只要求最大权闭合图,相信多数人已经基本理解了建图和求解的方法。但紧接着,怎样输出具体选择的实验和对应的实验仪器呢。对于不了解dinic算法原理的同学来说,其实这一步的求解可能是很困难的。

首先给出两种思路:

1. 只求出选择的实验编号,记录选择的实验对应的所有实验仪器编号即可。

2. 分别求出选择的实验编号以及选择的实验仪器编号。

第一种思路比较直观,但实际上,这道题中实验和实验仪器是可以一起求好的,也就是其实第二种思路也很简单。

但无论你想求实验还是实验仪器,都有一个共有的要求:理解dinic算法的原理。这里简单口胡一下。


dinic方法可以看成从S向T流出一条流量(为了流的正确性要先BFS处理),然后把边的容量更新为剩余容量(容量 - 流量),反向边加上流量以记录总流量;然后再重复从S向T流出一条流量(依旧先BFS处理),直到容量小到S流不到T那就结束。

而BFS的作用是沿着剩余容量在每次从S向T流出一条流量之前先处理出分层图,相当于求出从S到可达的每个点最短经过了多少点,经过几个点层次就是几;如果BFS不到T点(因为容量一直在更新减小)就结束,而且这时T点分层一定是0。

当然有的dinic把没有BFS到的点初始化为-1,S初始化为0,道理也是一样的。


所以我们发现,如果从S点流出到正权点(实验)的剩余容量=0,那么最后一次BFS一定不可以把这个正权点的层次更新,这个点的层次和T点一样是0。

那么,这样流满(剩余容量=0)的正权点(实验)选不选呢——答案是一定不需要选!因为:实验的获利 = 该边的容量 - 求最大流后边的流量!换言之:实验的获利 = 该边的剩余流量

至于原因,我们可以这么想:本来S到实验的容量表示的是做这个实验可以赚的钱;如果某个实验不需要任何实验仪器,那么我们不就把该边的剩余容量全部赚走了吗!如果这个实验需要且仅需要一个实验仪器,那么这个实验仪器价格的流量就会从S点 - > 实验的正权点 - > 仪器的负权点 - > T点!当这个仪器价格 < 这个实验赚的钱时,仪器 - > T点的边一定会流满,此时S点 - > 实验的剩余容量 = 容量 - 仪器的价格。所以,如果S - > 正权点的剩余流量=0时,我们这个实验不但赚不到钱,甚至有可能实验仪器边的流量没留满——也就是你买实验仪器其实需要倒贴钱!

由此得出,我们选的实验一定是有剩余流量的正权点,等价于层数不等于0

那么我们选的实验仪器又是哪些呢?我们已经可以按照思路1求出实验仪器集合了,但观察我们可以发现:如果某个实验有剩余流量,那么这些剩余流量会让BFS进行到这个实验连接的负权点(仪器)上——因为正权点和负权点之间的边一定有剩余容量(而且是无穷大)。由此得出,我们选的实验仪器一定是能BFS到的负权点,等价于层数不等于0

解说完毕!

代码如下:

对这类问题,如果不会网络流和方法,看上去真的会毫无思路吧——足以体现学习网络流的重要性。共勉。

大家也可以保存下来作为自己的dinic模板

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 10005, maxm = 100005, INF = 0x3f3f3f3f;
struct Edge{
    ll next,to,f;
}edge[maxm*2];
ll n,m,head[maxn],tot;
void INIT(){
    memset(head,-1,sizeof(head));
    tot=0;
}
void insert(int u,int v,int w){
    edge[tot].to = v, edge[tot].f = w, edge[tot].next = head[u], head[u] = tot;
    tot++;
    edge[tot].to = u, edge[tot].f = 0, edge[tot].next = head[v], head[v] = tot;
    //有向图反向弧容量初始化为0
    tot++;
}
ll level[maxn];
bool makelevel(ll s,ll t){
    memset(level,0,sizeof(level));
    level[s]=1;
    ll que[maxn];
    ll iq=0;
    que[iq++] = s;
    ll i,k;
    ll top;
    for(i=0; i<iq; i++){
        top = que[i];
        if(top == t) return true;
        for(k=head[top]; k!=-1; k=edge[k].next){
            if(! level[edge[k].to] && edge[k].f){
                que[iq++] = edge[k].to;
                level[edge[k].to] = level[top]+1;
            }
        }
    }
    return false;
}
ll DFS(ll now, ll maxf, ll t){
    if(now == t) return maxf;
    ll ret=0,f;
    ll k;
    for(k = head[now]; k!=-1; k=edge[k].next){
        if(edge[k].f && level[edge[k].to] == level[now] + 1){
            f = DFS(edge[k].to, min(maxf-ret, edge[k].f), t);
            edge[k].f -= f;
            edge[k^1].f += f;
            ret += f;
            if(ret == maxf) return ret;
        }
    }
    return ret;
}

ll DINIC(int s, int t){
    ll ans=0;
    while(makelevel(s,t)) ans += DFS(s,INF,t);
    return ans;
}
ll ex[maxn];
vector<ll> ins[maxn];
bool use[maxn];
int main(){
    cin >> m >> n;
    ll sum=0;
    /* 
    m为实验总数,n为仪器总数
    因为实验对应的仪器数不确定,试一下刚看到的cin.peek()函数 
    */
    INIT();
    for(ll i=1;i<=m;i++){
        cin >> ex[i];
        sum += ex[i];//注意sum是所有正权值之和 
        insert(0,i,ex[i]);
        while(cin.peek()!='\n'){
            ll num;
            cin >> num;
            ins[i].push_back(num);
            insert(i,num+m,INF);
        }
    }
    for(int i=1;i<=n;i++){
        ll num;
        cin >> num;
        insert(i+m,m+n+1,num);
    }
    sum -= DINIC(0,n+m+1);
    for(int i=1;i<=m;i++){
        if(level[i]!=0)printf("%lld ",i); //好好理解这一步骤吧 
    }
    printf("\n");
    for(int i=m+1;i<=n+m;i++){
        if(level[i]!=0)printf("%lld ",i-m);
    } 
    printf("\n%lld\n",sum);
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值