太空飞行计划LibreOJ - 6001(网络流24题)(最大权闭合图)

题目描述

W 教授正在为国家航天中心计划一系列的太空飞行。每次太空飞行可进行一系列商业性实验而获取利润。现已确定了一个可供选择的实验集合 E=\left \{ E_1,E_2,\cdot \cdot \cdot ,E_m \right \} ,和进行这些实验需要使用的全部仪器的集合 I =\left \{I_1,I_2,\cdot \cdot \cdot ,I_n \right \}。实验 E_j 需要用到的仪器是 I 的子集 R_j\in I

 

配置仪器 I_k 的费用为 c_k​ 美元。实验 E_j 的赞助商已同意为该实验结果支付 p_j​ 美元。W 教授的任务是找出一个有效算法,确定在一次太空飞行中要进行哪些实验并因此而配置哪些仪器才能使太空飞行的净收益最大。这里净收益是指进行实验所获得的全部收入与配置仪器的全部费用的差额。

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

输入格式

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

输出格式

第 1 行是实验编号,第 2 行是仪器编号,数字之间用一个空格隔开。最后一行是净收益。

样例

InputOutput
2 3
10 1 2
25 2 3
5 6 7
1 2
1 2 3
17

数据范围与提示

1\le n,m\le50

做法:本题属于最大权闭合图问题,闭合图的定义解释如下:来自胡伯涛——

最小割模型在信息学竞赛中的应用

转化为网络流的方法如下:在原图点集的基础上增加源S和汇T,将原图中的每条有向边(u,v)替换为网络流中流量为INF的有向边,增加从源点S到原图中每个正权点v(w_v>0)的有向边,容量为w_v,增加从原图中负权点v到汇点T,容量为-w_v的有向边。则最大权闭合子图的权值和=原图的点权和-流网络的最小割(最大流) w(V_1)=\sum_{v\in V^+}^{} w_v+c[S,T]

原闭合图流网络图

如何判断实验i是否值得做,我们知道,在跑完最大流dinic算法后,剩下的残量图时不存在一条路径能从源点S到汇点T的,此时的图已经被分为了两部分,由贪心的思想我们可以知道,如果实验结果的收益大于仪器投入价值时,在残量图中实验i从源点出发通过残量图中路径的可达点,因此我们只需在每次DFS时将无法进行增广的点标记,使用BFS中的dis数组的值来判定点i是否在残量图中与源点S连通

 由上图可知,样例中的实验1,2,仪器3,4,5均与源点S连通。

AC代码:

注意数据读入时的处理。

/*
 author:wuzx
 */
 
#include<bits/stdc++.h>
#define ll long long
#define int long long
#define endl "\n"
#define P pair<int,int>
#define f first
#define s second
using namespace std;
typedef unsigned long long ull;
const int maxn=200010;
const int inf=0x3f3f3f3f;
const int mod=998244353;
int t;
int n,m,k;
struct dinic{
    const int nn;
    int INF = inf;
    struct Edge{
        int to,cap,flow;
        Edge(int to,int cap,int flow):to(to),cap(cap),flow(flow){}
    };
    vector<int> dis,cur;
    vector<Edge> e;
    vector<vector<int>> g;
    dinic(int n1):nn(n1),dis(n1+1),cur(n1+1),g(n1+1){}
    void add(int u,int v,int w)
    {
        g[u].emplace_back(e.size());
        e.emplace_back(v,w,0);
        g[v].emplace_back(e.size());
        e.emplace_back(u,0,0);
    }
    bool bfs(int st,int end)
    {
        fill(dis.begin(),dis.end(),-1);
        dis[st]=0;
        queue<int> q;
        q.push(st);
        while(!q.empty())
        {
            int u=q.front();q.pop();
            for(int i:g[u])
            {
                auto [v,w,fo]=e[i];
                if(dis[v]==-1&&w>0)
                {
                    q.push(v);
                    dis[v]=dis[u]+1;
                }
            }
        }
        return dis[end]!=-1;//若不可以到终点(起点)就返回false 
    }
    int dfs(int st,int end,int flo)//dfs就是求节点u在残量为flo时的最大增量
    {
        if(st==end)
            return flo;
        int delta=flo;
        for(int i=cur[st];i<(int)g[st].size();i++)
        {
            int j=g[st][i];
            auto [v,w,fo]=e[j];
            cur[st]++;
            if((dis[v]==dis[st]+1)&&w>0)
            {
                int d=dfs(v,end,min(delta,w));
                if(!d)
                {
                    dis[v]=-1;
                    continue;
                }
                e[j].cap-=d;
                e[j^1].cap+=d;
                e[j].flow+=d;
                e[j^1].flow-=d;
                delta-=d;
                if(delta==0)
                    break;
            }
        }
        return flo-delta;
    }
    int max_flow(int st,int end)
    {
        int maxflow=0;
        while(bfs(st,end))
        {
            fill(cur.begin(),cur.end(),0);
            maxflow+=dfs(st,end,INF);
        }
        return maxflow;
    }
};
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    cin>>n>>m;
    dinic solve(n+m+2);
    int st=n+m+1,end=n+m+2;
    int ans=0;
    vector<vector<int> > yq(n+1);
    for(int i=1;i<=n;i++)
    {
        int num;
        cin>>num;
        ans+=num;
        solve.add(st,i,num);
        string ss;
        getline(cin,ss);
        for(int j=0;j<ss.length();)
        {
            if(ss[j]!=' ')
            {
                int kk;
                string s1="";
                for(kk=j;kk<ss.length();kk++)
                {
                    if(ss[kk]!=' ')
                        s1+=ss[kk];
                    else
                        break;
                }
                j=kk;
                int nb=stoi(s1);
                yq[i].push_back(nb);
                solve.add(i,nb+n,inf);
            }
            else
                j++;
        }
    }
    for(int i=1;i<=m;i++)
    {
        int num;
        cin>>num;
        solve.add(i+n,end,num);
    }
    ans-=solve.max_flow(st,end);
    for(int i=1;i<=n+m;i++)
        cout<<solve.dis[i]<<" ";
    cout<<endl;
    for(int i=1;i<=n;i++)
        if(solve.dis[i]!=-1)
            cout<<i<<" ";
    cout<<endl;
    for(int i=n+1;i<=n+m;i++)
        if(solve.dis[i]!=-1)
            cout<<i-n<<" ";
    cout<<endl;
    cout<<ans<<endl;
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值