2020牛客多校第一场H题Minimum-cost Flow(费用最小流)

题意: 给一个n个点m条边的有向带权图,q次询问,每次规定每条边的容量为u/v,你需要输出总流量为1时,从点1到点n的最小费用(分数表示),若到达的流量不足为1,则输出NaN;

题解:标准费用流模板,但是q的范围是1e5,所以我们要先跑一次费用流,把有用的信息记录下来,即把每一条增广路径的费用记录下来。

考虑放缩,同时乘以v,则总流量为v,每条边的容量为u,这是算出来的总费用除以v即为答案。我们可以在询问之前,预处理得到所有增广路的费用,每次进行一次SPFA算法后,就能得到一条增广路的费用,将其记录于path数组中(按增广顺序能保证每条增广路费用是升序的),并求其前缀和,方便后续询问的处理。

查询之前,先预处理得到:
(1)path[a],下标从0开始,表示单位容量(流量跑满,流量=容量)时第a+1条增广路费用。
(2)sum[a],下标从1开始,表示单位容量(流量跑满,流量=容量)时前a条增广路总费用。

接下来处理每次询问。
假设v=a*u+b(b<u),即前a条增广路流量都为u,第a+1条增广路流量达不到u,流量为b。
由于sum和path是之前预处理得到的单位容量情况下的费用,所以sum[a]要乘以u得到流量为u时前a条增广路总费用,path[a]要乘以b得到流量为b时第a+1条增广路费用,则ans=(sum[a]*u+path[a]*b)/v。

卡int。

题目链接 https://ac.nowcoder.com/acm/contest/5666/H

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF=INT_MAX;
const int maxn=1000+5,maxm=10000+5;
int n,m,s,t,q,cnt=0;
int head[maxn];
int dis[maxn];
ll maxflow=0;
ll minfee=0;
bool vis[maxn];
vector<ll> path;
ll sum[maxn];

struct node
{
    int e;
    int nxt;
    int w;//指权值 
    int f;//指流量 
}edge[maxm<<1];
inline void add(int u,int v,int flow,int cost)
{
    edge[cnt].e=v;
    edge[cnt].w=cost;
    edge[cnt].f=flow;
    edge[cnt].nxt=head[u];
    head[u]=cnt++;
}
inline int read()
{
   int ans=0;
   char r=getchar();
   while(r<'0'||r>'9')r=getchar();
   while(r>='0'&&r<='9')
   {
      ans=ans*10+r-'0';
      r=getchar();
   }
   return ans;
}
bool spfa(int u,int v)
{
    for(int i=0;i<maxn;i++)dis[i]=INF;//spfa基本操作
    memset(vis,false,sizeof(vis));//spfa的队列排重优化
    dis[u]=0;
    deque<int>d;//spfa的slf优化
    d.push_back(u);
    vis[u]=true;
    while(!d.empty())
    {
        int ui=d.front();d.pop_front();vis[ui]=false;//ui是当前松弛的边的起点
        for(int i=head[ui];i!=-1;i=edge[i].nxt)
        {
            int vi=edge[i].e;int w=edge[i].w;int f=edge[i].f;//vi是当前松弛的边的终点 
            if(f&&dis[vi]>dis[ui]+w)//如果当前边有残量且是最短路上的边就对其进行松弛
            {
                dis[vi]=dis[ui]+w;
                if(!vis[vi])
                {
                    if(!d.empty()&&dis[vi]<dis[d.front()])d.push_front(vi);
                    else d.push_back(vi);
                }
            }
        }
    }
    return dis[t]!=INF;
}
int dfs(int u,int flow)
{
    if(u==t)return flow;
    int detla=flow;
    vis[u]=true;
    for(int i=head[u];i!=-1;i=edge[i].nxt)
    {
        int v=edge[i].e;int w=edge[i].w;int f=edge[i].f;
        if(f&&!vis[v]&&dis[v]==dis[u]+w)//注意,一个点只能访问一次,这个可以用vis来维护(vis在spfa模块和dfs模块中都被用到,注意初始化)
        {
            int d=dfs(v,min(f,detla));
            detla-=d;
            edge[i].f-=d;edge[i^1].f+=d;
            minfee+=d*w;//更新费用(单价x流量)
            if(detla==0)break;
        }
    }
    vis[u]=false;
    return flow-detla;
}
ll dinic()
{
    maxflow=0;
    path.clear();
    while(spfa(s,t)){
        path.push_back(dis[t]); //记录增广路径的费用 
        memset(vis,false,sizeof(vis));
        maxflow+=dfs(s,INF);
    }
    return maxflow;
    printf("%d %d",maxflow,minfee);
}

int main()
{
    while(scanf("%d%d",&n,&m)!=EOF){
        memset(head,-1,sizeof(head));
        sum[0]=0;
        cnt=0;
        s=1,t=n; 
        int ui,vi,wi=1,fi;
        for(int i=0;i<m;i++){
            ui=read();vi=read();fi=read();//fi是单位流量费用,wi是最大流量 
            add(ui,vi,wi,fi);//正向边的权值为fi,流量为wi
            add(vi,ui,0,-fi);//反向边的权值为-fi,流量为0 
        }
        dinic();
        ll pnum=path.size(),u,v;
//        for(int i=0;i<pnum;i++){
//            cout<<path[i]<<" ";
//        } 
//        cout<<endl;
        for(ll i=0;i<pnum;i++){
            sum[i+1]=sum[i]+path[i];
        }
        scanf("%d",&q);
        while(q--){
            scanf("%lld%lld",&u,&v);
            if(u*pnum<v) printf("NaN\n");
            else{
                ll a=v/u;
                ll b=v%u;
                ll ans=sum[a]*u+path[a]*b;
                ll k=__gcd(ans,v);
                ans/=k;
                v/=k;
                printf("%lld/%lld\n",ans,v);
            }
        }
    }
    
   return 0;
}
View Code

 

posted @ 2020-07-31 22:44  杰瑞与汤姆  阅读(249)  评论(0编辑  收藏  举报