洛谷 P2604 [ZJOI2010]网络扩容 费用流 残图基础上再连边 Dinic+Spfa

题目链接:

https://www.luogu.com.cn/problem/P2604

思路参考博客:

https://www.luogu.com.cn/blog/hyblog/solution-p2604

算法:1:费用流 残图基础上再连边 Dinic+Spfa

思路:

1:第一问直接dinic就是了,重点就在于第二问

2:我们把第一问的残量网络继续利用,其中的每条边的费用都是0,此时我们再在第 i 条边的两端之间在建一条边,边的容量是 inf ,费用就是 costi​ 。这样我们固然可以保证费用正确,可是我们保证不了扩容了k,我们就可以建一个超级源点n+1,连向1号点,容量为k,费用为0。在这个网络中最大流一定是满流,也就是k啊,此时的最小费用就是我们所要求的

#include <bits/stdc++.h>

using namespace std;

const int maxn=1e3+2,maxm=2e4+3,inf=0x7fffffff;
int n,m,k,tot,next[maxm],beg[maxm],head[maxn],flow[maxm],fflow[maxm],last[maxn];
int pre[maxn],fl[maxn],nxt[maxm],to[maxm],ccost[maxm],cost[maxm],d[maxn],dep[maxn];
bool vis[maxn];

void addedge(int x,int y,int z,int co,int type)
{
    nxt[++tot]=head[x];head[x]=tot;to[tot]=y;beg[tot]=x;flow[tot]=z;cost[tot]=type?co:0;
    fflow[tot]=type?z:0;ccost[tot]=co;
}

bool bfs()
{
    memset(dep,0,sizeof(dep));
    queue<int> q;q.push(1);dep[1]=1;
    while(!q.empty())
    {
        int x=q.front();q.pop();
        for(int i=head[x];i;i=nxt[i])
        {
            int u=flow[i],v=to[i];
            if(u>0 and !dep[v])
            {
                dep[v]=dep[x]+1;q.push(v);
            }
        }
    }
    return dep[n];
}

int dfs(int x,int mini)
{
    if(x==n)return mini;
    for(int i=head[x];i;i=nxt[i])
    {
        int u=flow[i],v=to[i];
        if(u>0 and dep[v]==dep[x]+1){
            int dd=dfs(v,min(mini,u));
            if(dd>0)
            {
                flow[i]-=dd;flow[i^1]+=dd;
                return dd;
            }
        }
    }
    return 0;
}

int dinic()
{
    int ret=0;
    while(bfs())
    {
        int tmp=dfs(1,inf);
        while(tmp)
        {
            ret+=tmp;
            tmp=dfs(1,inf);
        }
    }
    return ret;
}

bool spfa()
{
    memset(d,0x7f,sizeof(d));
    memset(fl,0x7f,sizeof(fl));
    memset(vis,0,sizeof(vis));
    queue<int> q;q.push(n+1);vis[n+1]=1;
    d[n+1]=0;pre[n]=-1;
    while(!q.empty())
    {
        int now=q.front();q.pop();vis[now]=0;
        for(int i=head[now];i;i=nxt[i])
        {
            int v=to[i];
            if(fflow[i]>0&&d[v]>d[now]+cost[i])
            {
                d[v]=d[now]+cost[i];
                pre[v]=now;last[v]=i;
                fl[v]=min(fl[now],fflow[i]);
                if(!vis[v])
                {
                    vis[v]=1;q.push(v);
                }
            }
        }
    }
    return pre[n]!=-1;
}

int mcmf()
{
    int ret=0;
    while (spfa())
    {
        int now=n;ret+=fl[n]*d[n];
        while (now!=n+1)
        {
            fflow[last[now]]-=fl[n]; fflow[last[now]^1]+=fl[n];
            now=pre[now];
        }
    }
    return ret;
}

void rebuild()
{
    int cnt=tot;
    for(int i=2;i<=cnt;i+=2)
    {
        fflow[i]=flow[i];fflow[i+1]=flow[i+1];
        addedge(beg[i],to[i],inf,ccost[i],1);
        addedge(to[i],beg[i],0,-ccost[i],1);
    }
}

int main()
{
    scanf("%d %d %d",&n,&m,&k);
    addedge(n+1,1,k,0,1);
    for(int i=1;i<=m;i++)
    {
        int a,b,c,d;
        scanf("%d %d %d %d",&a,&b,&c,&d);
        addedge(a,b,c,d,0);
        addedge(b,a,0,-d,0);
    }
    int ans1=dinic();
    rebuild();
    int ans2=mcmf();
    printf("%d %d\n",ans1,ans2);
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值