2020牛客暑期多校训练营(第一场)H

Minimum-cost Flow

https://ac.nowcoder.com/acm/contest/5666/H

题目描述

  Bobo has a network of nodes and arcs. The -th arc goes from the -th node to the -th node, with cost .
  Bobo also asks questions. The -th question is specified by two integers and , which is to ask the minimum cost to send one unit of flow from the -st node to the -th node, when all the edges have capacity (a fraction).
  You can refer the wiki page for further information of Minimum-cost Flow.

输入描述

  The input consists of several test cases terminated by end-of-file.
  The first line of each test case contains two integers and . The -th of the following lines contains three integers and . The next line contains an integer . The -th of the last lines contains two integers and .
  , , , .
  , , .
  The sum of does not exceed . The sum of does not exceed .

输出描述

  For each test case, print fractions (or NaN, if it is impossible to send one unit of flow) which denote the answer.

示例1

输入

2 1
1 2 2
1
1 1
2 2
1 2 1
1 2 2
3
1 2
2 3
1 4

输出

2/1
3/2
4/3
NaN

分析

  运行 次最小费用流算法显然是不现实的,应当考虑的是:运行一次最小费用流,进行一些预处理,使得每次询问时能够在较短时间内给出答案。
  题中有一重要信息:每条边的容量都相同。设这个容量为 。利用 算法求最小费用最大流,会不断用 算法寻找单位费用和最小的增广路;由于每条边的容量都为 ,故每条边只会存在满流或零流的情况,每次增广获得的最大流改进量恒为 。若源点流量为 不超过最大流,就是最小费用流模型。对于这样特殊的(每条边容量都为 )最小费用流模型,仍然可以利用 算法,每次增广后相当于将流量 减少 ,产生的费用是增广路单位费用和与 的乘积;而最后一次增广时费用的改进量为剩余流量 与增广路单位费用和的乘积。进行多轮增广,直至剩余流量为零,就能得到最小费用流。
  由于每条边的费用 是不变的,因此不论 如何变化,只要源点流量不超过当前网络的最大流,对 次询问中的每一张网络进行 算法,找到的增广路都是相同的,且每条增广路的顺序也保持不变。因此,只需要进行一次最小费用流的计算,记录下每一条增广路的单位费用即可,根据 算法原理,这些增广路是按照单位费用和由小到大获得的,排序操作是多余的。
  不妨令每条边的容量为 ,求一次最小费用最大流,记录下每一条增广路的单位费用。对于每一次询问,源点流量为 ,每条边容量为 。为了减少计算的误差,可以将所有数量扩大 倍,即网络中源点流量为 ,每条边容量为 。若源点流量 不超过网络最大流,那么求固定流量 的最小费用流得过程中,增广路为最小费用最大流得增广路子集,且每一轮 获得的增广路一定是当前所有存在的增广路中费用最小的。每一轮增广,源点流量就减少 ,相应的费用为该增广路的单位费用与 的乘积;最后一次增广,源点流量减少 ,对应费用也要相应改变;最终,源点流量为 ,求得最小费用流 即为一次询问的答案。

代码

/******************************************************************
Copyright: 11D_Beyonder All Rights Reserved
Author: 11D_Beyonder
Problem ID: 2020牛客暑期多校训练营(第一场) Problem H
Date: 7/24/2020 
Description: Minimum-cost Flow
*******************************************************************/
#include<cstring>
#include<vector>
#include<algorithm>
#include<cstdio>
#include<queue>
using namespace std;
typedef long long ll;
const int N=104;
const int M=203;
const ll inf=0x3f3f3f3f3f3f3f3f;
int n,m;//n个点,m条边
int tot;
int head[N];
struct E
{
    int to;
    int Next;
    ll cap;//容量
    ll cost;//费用
}edge[M<<2];
vector<ll>span;//增广路单位费用
int pre[N];//记录增广路前驱
ll incf[N];//增广路上剩余最小容量
ll dis[N];//将费用看作边权求最短路
bool vis[N];//记录访问
int s,t;//源点 汇点
ll maxflow,mincost;//最小费用最大流
inline void init();//初始化
inline void add_edge(int,int,int,int);
bool SPFA();//找增广路
void update();//更新最长增广路容量
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        init();
        while(m--)
        {
            int u,v,c;
            scanf("%d%d%d",&u,&v,&c);
            add_edge(u,v,1,c);
            add_edge(v,u,0,-c);
        }
        while(SPFA())
        {
            update();
            //记录每一条增广路的单位费用
            span.push_back(dis[t]);
        }
        int q;
        scanf("%d",&q);
        while(q--)
        {
            ll u,v;
            scanf("%lld%lld",&u,&v);
            //流量v 容量u
            if(maxflow*u<v) puts("NaN");//流量大于最大流
            else
            {
                ll flow=v,cap=u;
                ll ans=0;
                //按照费用由小到大的顺序访问所有增广路
                for(auto cost:span)
                {
                    if(flow>=u)
                    {
                        flow-=u;
                        ans+=u*cost;
                    }
                    else 
                    {
                        ans+=flow*cost;
                        break;
                    }
                }
                //输出最简分式
                ll g=__gcd(ans,v);
                printf("%lld/%lld\n",ans/g,v/g);
            }
        }
    }
    return 0;
}
inline void init()
{
    s=1;
    t=n;
    memset(head,-1,sizeof(head));
    tot=1;
    span.clear();
    memset(pre,0,sizeof(pre));
    maxflow=mincost=0;
}
inline void add_edge(int u,int v,int cap,int cost)
{
    tot++;
    edge[tot].to=v;
    edge[tot].cap=cap;
    edge[tot].cost=cost;
    edge[tot].Next=head[u];
    head[u]=tot;
}
bool SPFA()
{
    queue<int>q;
    memset(dis,inf,sizeof(dis));
    memset(vis,0,sizeof(vis));
    q.push(s);
    dis[s]=0;
    vis[s]=1;
    incf[s]=inf;
    while(!q.empty())
    {
        int x=q.front();
        vis[x]=0;
        q.pop();
        for(register int i=head[x];~i;i=edge[i].Next)
        {
            if(!edge[i].cap) continue;//剩余容量为0,不在残量网络中
            int y=edge[i].to;
            if(dis[y]>dis[x]+edge[i].cost)
            {
                dis[y]=dis[x]+edge[i].cost;//松弛操作
                incf[y]=min(incf[x],edge[i].cap);//最小剩余容量
                pre[y]=i;//记录前驱
                if(!vis[y])
                {
                    vis[y]=1;
                    q.push(y);
                }
            }
        }
    }
    if(dis[t]==inf) return 0;//汇点不可达,已经求出最大流
    else return 1;
}
void update()
{
    int x=t;
    //沿着前驱倒着走增广路
    while(x!=s)
    {
        int y=pre[x];
        edge[y].cap-=incf[t];
        edge[y^1].cap+=incf[t];
        x=edge[y^1].to;
    }
    maxflow+=incf[t];
    mincost+=dis[t]*incf[t];
}
牛客暑期多校训练营题解 文章被收录于专栏

收集牛客暑期多校训练营的题解

全部评论

相关推荐

双非一本失业第二年:《机器视觉垃圾分类》
点赞 评论 收藏
分享
评论
点赞
收藏
分享
牛客网
牛客企业服务