【网络流24题】B、太空飞行计划问题(最大权闭合图转最小割、最小割方案输出)

整理的算法模板合集: ACM模板


B、太空飞行计划问题(最大权闭合图转最小割、最小割方案输出)【省选/NOI- 】

P2762 太空飞行计划问题

在这里插入图片描述

【问题分析】

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

【建模方法】

把每个实验看作二分图X集合中的顶点,每个设备看作二分图Y集合中的顶点,增加源S和汇T。
1、从S向每个Xi连接一条容量为该点收入的有向边。
2、从Yi向T连接一条容量为该点支出的有向边。
3、如果一个实验i需要设备j,连接一条从Xi到Yj容量为无穷大的有向边。

统计出所有实验的收入只和Total,求网络最大流Maxflow,最大收益就是Total-Maxflow。对应的解就是最小割划分出的S集合中的点,也就是最后一次增广找到阻塞流时能从S访问到的顶点。

【建模分析】

定义一个割划分出的S集合为一个解,那么割集的容量之和就是(未被选的A集合中的顶点的权值 + 被选的B集合中的顶点的权值),记为Cut。A集合中所有顶点的权值之和记为Total,那么Total - Cut就是(被选的A集合中的顶点的权值 - 被选的B集合中的顶点的权值),即为我们的目标函数,记为A。要想最大化目标函数A,就要尽可能使Cut小,Total是固定值,所以目标函数A取得最大值的时候,Cut最小,即为最小割。

该问题的一般模型为最大权闭合图,相关讨论见《最小割模型在信息学竞赛中的应用》作者胡伯涛。

【反思与总结】

割掉的实验仪器就是用到的实验仪器,割掉的实验就是不做的实验,所以我们所有割掉的就都是花费,我们用总预计收益减去最小花费就得到了最大收益。
对于所有从源点到汇点的路,我们要么割掉实验,表示不做这个实验,要么割掉实验仪器,表示买这个仪器,那么我们所有需要这个仪器的实验(与之相连的实验)就可以做了。
由于实验及器材之间的边是不可割的,因此a(要用的实验器材)一定在A(要做的实验)的点集中;而b(不要用的实验器材)与A(要做的实验)间并无连边,因此无法通过A(要做的实验)与源点联通,就只能与B(所有不做的实验)属于同一集合啦!
我们这样给边赋值,对于每个割,用所有实验的奖金( w A + w B w_A+w_B wA+wB​ )减去该割( w B + w a w_B+w_a wB+wa )就是该方案的答案( w A − w a w_A-w_a wAwa ),最大化答案就是最小化割值。

最后的输出方案:
用Dninc求最大流的好处是便于我们输出,因为层数如果为INF,那么显然该边容量大于0,这就说明这条边并没有流量流过,那么显然与它相连的实验或者器材没有被使用(因为使用过的话,边的容量会变为0),直接输出即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>

using namespace std;
typedef long long ll;
const int N = 500007, M = 5000007, INF = 0x3f3f3f3f;

inline int read(int &x)
{
    
    
    char c;x = 0;
    while(c < '0' || c > '9')c = getchar();
    while(c >= '0' && c <= '9'){
    
    x = x * 10 + c - '0';c = getchar();}
    return c == '\r' || c == '\n' ? 0 : 1;
}

int n, m;
int head[N], ver[M], nex[M], tot;
int deep[N];
int cur[N], S, T;
ll maxflow, edge[M];

void add(int x, int y, int z, bool o = 1){
    
    
    ver[tot] = y;
    edge[tot] = z;
    nex[tot] = head[x];
    head[x] = tot ++ ;
    if(o)add(y, x, 0, 0);
}

bool bfs()
{
    
    
    memset(deep, 0x3f, sizeof deep);
    queue<int>q;
    q.push(S), deep[S] = 0, cur[S] = head[S];
    while(q.size())
    {
    
    
        int x = q.front();
        q.pop();
        for(int i = head[x]; ~i; i = nex[i]){
    
    
            int y = ver[i], z = edge[i];
            if(z > 0 && deep[y] == INF){
    
    
                q.push(y);
                deep[y] = deep[x] + 1;
                if(y == T)return true;
            }
        }
    }
    return false;
}

ll dfs(int x, ll flow)
{
    
    
    if(x == T)return flow;
    ll ans = 0, i, k;
    for(i = cur[x]; ~i && flow; i = nex[i]){
    
    
        cur[x] = i;
        int y = ver[i];
        ll z = edge[i];
        if(edge[i] > 0 && (deep[y] == deep[x] + 1))
        {
    
    
            k = dfs(y, min(flow, z));
            if(!k)deep[y] = 0;//待定
            edge[i] -= k;
            edge[i ^ 1] += k;
            ans += k;
            flow -= k;
        }
    }
    if(!ans)deep[x] = INF;//流量不为零,说明不是最小割
    return ans;
}

void dinic()
{
    
    
    while(bfs()){
    
    
        for(int i = 1; i <= n + m + 1; ++ i)
            cur[i] = head[i];
        maxflow += dfs(S, INF);
    }

}

int main()
{
    
    
    memset(head, -1, sizeof head);
    scanf("%d%d", &n, &m);
    S = 0, T = n + m + 1;
    ll sum = 0;

    for(int i = 1; i <= n; ++ i){
    
    
        int x, ch;
        ch = read(x);
        sum += x;
        add(S, i, x);
        while(ch){
    
    
            ch = read(x);
            add(i, x + n, INF);
        }
    }
    for(int i = 1; i <= m; ++ i){
    
    
        int x;
        scanf("%d", &x);
        add(i + n, T, x);
    }
    dinic();

    ll ans = sum - maxflow;


    for(int i = 1; i <= n; ++ i)
        if(deep[i] != INF)printf("%d ", i);
    puts("");
    for(int i = 1; i <= m; ++ i)
        if(deep[n + i] != INF)printf("%d ", i);
    puts("");
    printf("%lld\n", ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_45697774/article/details/108653378
今日推荐