【dinic+KP,最大流+费用流】 [ZJOI2010]网络扩容

链接

https://www.luogu.org/problemnew/show/P2604

大意

  1. 给定一张图,求其的最大流
  2. 给定改动每条边的价值,在使最大流增加 K 的情况的最小价值

思路

不得不说,这是一个练习网络流的好题
对于第一问,直接跑一遍最大流
对于第二问,新增一群容量无穷大的没有代价的边(也就是相当于不会删掉它们),然后再新建一个点,将其连向汇点,代价为0,容量为题目所需的增加后的最大流,那么这个时候再跑一遍费用流,其会选择这条代价为0,容量为需要的流量的边,或者是代价更下的边,这样就确保了一定有一条最大流满足最小费用

代码

#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
#define N 50001
#define reads(a,b,c,d) a=read();b=read();c=read();d=read()
using namespace std;int f,n,m,s,t,ans1,ans2,u[5001],v[5001],w[5001],g[5001],k;char c;
int read()
{
    f=0;
    while(c=getchar(),c<=47||c>=58);f=(f<<3)+(f<<1)+c-48;
    while(c=getchar(),c>=48&&c<=57) f=(f<<3)+(f<<1)+c-48;
    return f;
}
struct node{int next,to,w,g;}e[N<<1];
int dis[N],l[N],cf[N],tot,pos[N],d[N];
bool vis[N];
void add(int u,int v,int w,int c)//建边
{
    e[tot]={l[u],v,w,c};l[u]=tot++;
    e[tot]={l[v],u,0,-c};l[v]=tot++;
    return;
}
bool bfs()//建造分层图
{
    memset(d,-1,sizeof(d));
    queue<int>q;d[s]=0;q.push(s);
    while(q.size())
    {
        int x=q.front();q.pop();
        for(int i=l[x];~i;i=e[i].next)
        {
            int y=e[i].to;
            if(e[i].w&&d[y]==-1)
            {
                d[y]=d[x]+1;
                q.push(y);
                if(y==t) return true;
            }
        }
    }
    return false;
}
int dfs(int x,int flow)//求可行流
{
    if(x==t||!flow) return flow;
    int rest=0,k=0;
    for(int i=l[x];~i;i=e[i].next)
    {
        int y=e[i].to;
        if(d[x]+1==d[y]&&e[i].w)
        {
            f=dfs(y,min(flow-rest,e[i].w));
            if(!f) d[y]=-1;
            e[i].w-=f;rest+=f;e[i^1].w+=f;
        }
    }
    if(!rest) d[x]=-1;
    return rest;
}
int dinic()//求最大流
{
    int r=0;
    while(bfs()) r+=dfs(s,50234567);
    return r;
}
bool spfa()//KE算法求费用流
{
    fill(dis+1,dis+1+t,50234567);
    memset(vis,0,sizeof(vis));
    queue<int>q;q.push(s);dis[s]=0;vis[s]=true;cf[s]=50234567;
    while(q.size())
    {
        int x=q.front();q.pop();vis[x]=true;
        for(int i=l[x];~i;i=e[i].next)
        {
            int y=e[i].to,w=e[i].g;
            if(e[i].w&&dis[y]>dis[x]+w)
            {
                dis[y]=dis[x]+w;
                pos[y]=i;
                cf[y]=min(cf[x],e[i].w);
                if(!vis[y]) q.push(y),vis[y]=true;
            }
        }
        vis[x]=false;
    }
    return dis[t]<50234567;
}
void updata()//更改每条边的容量
{
    int x=t;
    while(x!=s)
    {
        int i=pos[x];
        e[i].w-=cf[t];
        e[i^1].w+=cf[t];
        x=e[i^1].to;
    }
    ans2+=dis[t]*cf[t];//累计答案
    return;
}
void EK()//求费用流
{
    while(spfa()) updata();return;
}
int main()
{
    memset(l,-1,sizeof(l));
    n=read();m=read();k=read();s=1;t=n;//输入
    for(int i=1;i<=m;i++) {reads(u[i],v[i],w[i],g[i]);add(u[i],v[i],w[i],0);}//建边
    ans1=dinic();//求最大流
    memset(&e,0,sizeof(e));tot=0;memset(l,-1,sizeof(l));//废掉原图,重新建图
    for(int i=1;i<=m;i++) add(u[i],v[i],1e9,g[i]),add(u[i],v[i],w[i],0);//建上两种边
    add(n,n+1,k+ans1,0);t=n+1;//新增一个点,连接汇点
    EK();//求费用流
    printf("%d %d",ans1,ans2);//输出,endl
}

猜你喜欢

转载自blog.csdn.net/xuxiayang/article/details/80775783