2020 牛客多校第一场H

题意

给定一张图,求最小费用流(源点流量为 1 1 1)
多个询问,每个询问将所有边的容量变为 u v \frac{u}{v} vu,求费用

题解

首先我们考虑费用流,如果所有边容量相同,为 c a p cap cap
那么如果源点流量够多,每次增广得到的改进量是不会变化的,恒为 c a p cap cap
回过来考虑这道题:对于源点流量有限的情况,在前面够的时候为 c a p cap cap,不够的时候就是剩余的流量 f l o w flow flow

对于某个询问,容量 c a p = u v cap=\frac{u}{v} cap=vu,流量为 1 1 1
我们将流量和容量都扩大 v v v倍,流量为 v v v,容量为 u u u,显然最后的花费也会被扩大 v v v倍(因为整张图没有发生实际的变化,所以费用流的实际过程并没有变化)
这个时候我们每次增广 u u u,直到不够为止,就可以得到结果。

具体做法为:
1、令容量为 1 1 1,求出单位情况下费用流的过程,记录下每次增广花费的费用。(这里我们直接建图,相当于源点的流量是无穷多的)
2、实际询问的时候就是在已知整个过程的基础上,每次增广 u u u,剩下流量就为 v − u v-u vu,然后继续增广,直到没有为止。(记录的时候是无穷多流量,但实际询问的时候流量只有 v v v)
3、如果最后流量仍然还剩,等于告诉你,无穷多流量都已经没得跑了,这里的 v v v多的等于是无穷多了,也就是你跑不满最大流了,输出错误。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn = 5e5 + 500;
const int mod = 1e9 + 7;
const ll inf = 1e18;

struct Edge{
    int from,to;
    ll cap,flow,cost;
};

vector<ll>mp;

struct MCMF{
    int n,tmp,s,t;
    vector<Edge>edges;
    vector<int>G[maxn];
    int inq[maxn];
    ll d[maxn];//spfa
    int p[maxn];//上一条弧便于回溯
    ll a[maxn];//最小改进量

    void init(int n,int s,int t){
        this->n=n,this->s=s,this->t=t;
        edges.clear();
        for(int i=1;i<=n;i++)G[i].clear();
    }

    void AddEdge(int from,int to,ll cap,ll cost){
        edges.push_back((Edge){from,to,cap,0,cost});
        edges.push_back((Edge){to,from,0,0,-cost});
        tmp=edges.size();
        G[from].push_back(tmp-2);
        G[to].push_back(tmp-1);
    }

    bool spfa(int s,int t,ll& flow,ll& cost){
        for(int i=0;i<=n;i++)d[i]=inf;
        memset(inq,0,sizeof(inq));
        d[s]=0,inq[s]=1,p[s]=0,a[s]=inf;
        queue<int>Q;
        Q.push(s);
        while(!Q.empty()){
            int u=Q.front();Q.pop();
            inq[u]=0;
            for(int i=0;i<G[u].size();i++){
                Edge& e=edges[G[u][i]];
                if(e.cap>e.flow&&d[e.to]>d[u]+e.cost){
                    d[e.to]=d[u]+e.cost;//松弛
                    p[e.to]=G[u][i];//记录上一条弧
                    a[e.to]=min(a[u],e.cap-e.flow);//最小可改进量
                    if(!inq[e.to]){Q.push(e.to);inq[e.to]=1;}//入队
                }
            }
        }
        if(d[t]==inf)return false;//说明不连通了。
        flow+=a[t];//如果固定流量的话,可以在flow+a>=k的时候只增广到k,然后终止程序
        cost+=d[t]*a[t];
        int u=t;
        while(u!=s){
            edges[p[u]].flow+=a[t];
            edges[p[u]^1].flow-=a[t];
            u=edges[p[u]].from;
        }
        return true;
    }

    ll Mincost(){//绝对不能有负权圈,否则连续最短路的数学证明失效
        ll flow=0,cost=0;
        while(spfa(s,t,flow,cost)){
            mp.push_back(d[t]);
        }
        return flow;
    }
}mf;

int main(){
    int n,m;
    while(~scanf("%d%d",&n,&m)){
        mp.clear();
        mf.init(n,1,n);
        for(int i=1;i<=m;i++){
            int u,v;ll w;scanf("%d%d%lld",&u,&v,&w);
            mf.AddEdge(u,v,1,w);
        }
        mf.Mincost();
        int q;scanf("%d",&q);
        while(q--){
            int u,v;scanf("%d%d",&u,&v);
            ll ansu=0,ansv=v;
            for(int i=0;i<mp.size();i++){
                if(v>u){
                    v-=u;
                    ansu+=mp[i]*u;
                }
                else{
                    ansu+=mp[i]*v;
                    v=0;
                }
            }
            if(v){
                puts("NaN");
                continue;
            }
            ll gcd=__gcd(ansu,ansv);
            ansu/=gcd,ansv/=gcd;
            printf("%lld/%lld\n",ansu,ansv);
        }
    }
}

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值