[网络流24题]太空飞行计划问题——最大权闭合子图

Description:

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

思路:

看了半天没有看出来这是网络流的习题。然后上网看别人的博客发现这是一种从未接触过的算法。

建模——最大权闭合子图:

每一个仪器只要配置了一次就可以重复使用的,同时一个实验要进行的话需要全部的实验仪器,这算是题目的限制条件。发现就只这样说肯定是不好做的,我们可以转化为图论模型:实验分为m个点,每个点的权值为赞助商赞助的费用,仪器分为n个点,每个点的权值为负数,其绝对值为配置仪器的费用,每一个实验向它的所有仪器连一条有向边,然后构成了一个图。我们所要求的就是一个这个图的子集,使得集合中的点没有边连向外面且集合权值最大,就是所谓的最大权闭合子图。

如何利用网络流来构造闭合子图:

现在就已经建好模型了,考虑怎么解决这个问题。我们要选定一个子集使得和外面没有连边,考虑用一个网络流的模型来解决这个问题,将一些点和源点连边,其余的点和汇点连边,中间的图保持不变,这个网络图的简单割(即所有的割边都只和源点或者汇点相连)中,源点所在的那一部分子图必定是一个闭合图,因为在割中,删去了割边之后保证里两侧子图不存在流量,所以源点所在的那部分子图必定是不存在向集合外的出边的。

如何求解最大权闭合子图:

我们通过构造网络图然后寻找简单割的方式来求闭合子图,但是我们还要保证闭合子图的权值和最大。考虑寻找简单割的过程中,和源点相连的点被割开了之后相当于不选择这个点,和汇点相连的点被割开了之后相当于选择这个点,如果我们把正权值的点都和源点相连,连一条权值为点权的边,负权值的点都和汇点相连,连一条权值为权值的绝对值的边,那么最后的答案就是:所有正权值的和-(不选择的正权值的和+所有的选择了的负权值的绝对值的和),而我们就要让后面这个括号里的东西最小,不难发现,我们求的就是最小的割。但是如何保证最小割是简单割呢?把不和源点或者汇点相连的边的边权视为inf就好了。

解题过程:

所以这么下来就这么几点:

  1. 构建网络图,正权值和源点连边,负权值和汇点连边,权值为绝对值,中间的权值为inf。
  2. 不断地跑增广路来求最大流,最大流=最小割。
  3. 用总数减去最小割即可。

输出方案:

但是这题最烦人的就是方案,看了好久才看懂。。。。
最暴力的方法就是枚举删哪条边,然后再跑一遍网络流。
有一种办法是把流量满了边自动视为割边,然后BFS求源点联通块里的点。
但是这样我看明显是错的,但是确实是对的。。。
我认为错的原因有两个,但是后面都被我推伪了
这个最大流的图可能存在不止一个最小割,有可能会重复,然后发现:
不走流量满了的边的话确实只会算成一个最小割。。。
还有一个就是流量满了的边不一定是任何一个最小割中的边,然后发现:
出现上述情况时,一定会有另外一条和它相连的边从它的端点引出反向流,
然后这条边就算遍历了。。遍历了。。。
学习这道题的过程有点心酸。。。

/*==========================
 * Author : ylsoi
 * Problem : luogu2762
 * Algrithm : Flow
 * Time : 2018.6.21
 * =========================*/
#include<bits/stdc++.h>

using namespace std;

void File(){
    freopen("luogu2762.in","r",stdin);
    freopen("luogu2762.out","w",stdout);
}

template<typename T>bool chkmax(T &_,T __){return _<__ ? (_=__,1) : 0;}
template<typename T>bool chkmin(T &_,T __){return _>__ ? (_=__,1) : 0;}
#define REP(i,a,b) for(register int i=a;i<=b;++i)
#define inf (0x3f3f3f3f)
typedef long long ll;

const int maxn=50+10;
int ss,tt,cnte=1,ans,n,m,tot;
int to[maxn*maxn*2],last[maxn*maxn*2],beg[maxn*maxn],flow[maxn*maxn];

void add(int u,int v,int f){
    last[++cnte]=beg[u];
    beg[u]=cnte;
    to[cnte]=v;
    flow[cnte]=f;
}

struct dinic{
    int num[maxn*maxn],cur[maxn*maxn];
    bool bfs(){
        memset(num,0,sizeof(num));
        queue<int>qu;
        qu.push(ss);
        num[ss]=1;
        while(qu.size()){
            int u=qu.front();
            qu.pop();
            if(u==tt)return true;
            for(int i=beg[u];i;i=last[i]){
                if(num[to[i]] || !flow[i])continue;
                qu.push(to[i]);
                num[to[i]]=num[u]+1;
            }
        }
        return false;
    }
    int dfs(int u,int gap){
        if(u==tt || !gap)return gap;
        int sum=0,f;
        for(int &i=cur[u];i;i=last[i]){
            if(num[to[i]]!=num[u]+1)continue;
            if((f=dfs(to[i],min(gap,flow[i])))){
                flow[i]-=f;
                flow[i^1]+=f;
                sum+=f;
                gap-=f;
            }
            if(!gap)break;
        }
        return sum;
    }
    void work(){
        while(bfs()){
            REP(i,0,n+m+1)cur[i]=beg[i];
            ans+=dfs(ss,inf);
        }
    }
}T;

void init(){
    ss=0;
    REP(i,0,m){
        char tools[10000];
        memset(tools,0,sizeof tools);
        cin.getline(tools,10000);
        int ulen=0,tool;
        while (sscanf(tools+ulen,"%d",&tool)==1){
            if(i==0 && !m)m=tool;
            else if(i==0 && !n)n=tool;
            if(i){
                if(!ulen)add(ss,i,tool),add(i,ss,0),tot+=tool;
                else add(i,m+tool,inf),add(m+tool,i,0);
            }
            if(tool==0)ulen++;
            else{
                while(tool){
                    tool/=10;
                    ulen++;
                }
            }
            ulen++;
        }
    }
    tt=n+m+1;
    REP(i,1,n){
        int f;
        scanf("%d",&f);
        add(m+i,tt,f);
        add(tt,m+i,0);
    }
}

int main(){
    File();
    init();
    T.work();
    T.bfs();
    REP(i,1,m)if(T.num[i])printf("%d ",i);
    printf("\n");
    REP(i,m+1,m+n)if(T.num[i])printf("%d ",i-m);
    printf("\n");
    printf("%d\n",tot-ans);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值