ZOJ 2532 最小割

本文介绍了一种求解最大流问题的高效算法,通过分析残量网络中的割边来找出能够增加网络最大流的边。该方法避免了直接枚举边所带来的高复杂度,并进一步探讨了如何判断最小割是否唯一的技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【题目大意】 有 N 个城市,M 个中转站以及 L 条有向边(u, v, c),表示可以从 u 向 v 传送信息, 带宽为 c。每个城市都在向 CIA 总部发送无穷大的信息量,但是目前总部实际接 收带宽已经不能满足要求。CIA 决定要增大某条边的带宽以增大总部的接收带宽, 请找出哪些边带宽的增加能导致总部接收带宽的增加。(1 <= N+M <= 100, 1 <= L <= 1000)

【建模方法】 此题要求找出这样一条边,增加它的容量可以导致最大流的增加。最直观的做法 是先求一次最大流记为 ans,然后枚举每条边的容量加 1 再求最大流,看是否大 于 ans。但是这样做复杂度太高。有没有更好的办法?如果我们换个角度,考虑 求完最大流后残量网络 R 中的割,那么问题便迎刃而解。最大流的含义是我们现 在在 R 中找不到一条从 s 走到 t 的增广路。我们只要找到这样的割边(u, v),使得 R 中从 s 能走到 u,从 v 能走到 t,那么当我增加这条割边的容量后,它就会再次 出现在 R 中,架起一条增广路 s-u-v-t,从而增加最大流的流量。再正式描述一下 算法流程:求一次最大流,然后在残量网络 R 中以正向弧对 s、以正向弧的逆对 t 作 DFS,并用 from[i], to[i]标记点 i 能否从 s 可达,能否可达 t。扫描每一条正向 弧(u, v),若它残留容量为 0 且 from[u]且 to[v],则(u, v)为一个解。
发散思维:

如何判定网络的最小割是否唯一? 同样求一次最大流后在残量网络 R 中以正向弧对 s、以正向弧的逆对 t 作 DFS, 只不过这次只用一个 flag[i]标记点 i 是否在两次 DFS 中被探访过。最后扫一遍所 有点,如果存在没有被探访过的,则说明最小割不唯一。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <vector>
#include <cmath>
using namespace std;
const int N=1e5;
const int inf=0x3f3f3f3f;

struct node
{
    int to,cap,next,flow;
    node(){}
    node(int to,int cap,int next,int flow):to(to),cap(cap),next(next),flow(flow){}
}edge[N];
int tol;
int head[N],gap[N],dep[N],pre[N],cur[N];
bool from[N],to[N],vis[N];
void init()
{
    tol=0;
    memset(head,-1,sizeof(head));
}
void addedge(int u,int v,int w,int rw=0)
{
    edge[tol]=node(v,w,head[u],0);
    head[u]=tol++;
    edge[tol]=node(u,rw,head[v],0);
    head[v]=tol++;
}
int sap(int start,int _end,int N)
{
    memset(gap,0,sizeof(gap));
    memset(dep,0,sizeof(dep));
    memcpy(cur,head,sizeof(head));
    int u=start;
    pre[u]=-1;
    gap[0]=N;
    int ans=0;
    while(dep[start]<N)
    {
        if(u==_end)
        {
            int Min=inf;
            for(int i=pre[u];i!=-1;i=pre[edge[i^1].to])
                Min=min(Min,edge[i].cap-edge[i].flow);
            for(int i=pre[u];i!=-1;i=pre[edge[i^1].to])
            {
                edge[i].flow+=Min;
                edge[i^1].flow-=Min;
            }
            u=start;
            ans+=Min;
            continue;

        }

        bool flag=false;
        int v;
        for(int i=cur[u];i!=-1;i=edge[i].next)
        {
            v=edge[i].to;
            if(edge[i].cap-edge[i].flow&&dep[v]+1==dep[u])
            {
                flag=true;
                cur[u]=pre[v]=i;
                break;
            }
        }
        if(flag)
        {
            u=v;continue;
        }
        int Min=N;
        for(int i=head[u];i!=-1;i=edge[i].next)
            if(edge[i].cap-edge[i].flow&&dep[edge[i].to]<Min)
            {
                Min=dep[edge[i].to];
                cur[u]=i;
            }
        gap[dep[u]]--;
        if(!gap[dep[u]])return ans;
        dep[u]=Min+1;
        gap[dep[u]]++;
        if(u!=start)u=edge[pre[u]^1].to;

    }

    return ans;
}
queue<int>q;
vector<int>ans;
int main()
{
    //freopen("a.txt","r",stdin);
    int n,m,l;
    while(cin>>n>>m>>l)
    {
       if(n==0&&m==0&&l==0)break;
       init();
       for(int i=1;i<=l;i++)
       {
           int u,v,w;
           scanf("%d%d%d",&u,&v,&w);
           addedge(u,v,w);
       }
       for(int i=1;i<=n;i++)addedge(n+m+1,i,inf);
       int sum=sap(n+m+1,0,n+m+2);
       memset(from,0,sizeof(from));
       memset(to,0,sizeof(to));
       while(!q.empty())q.pop();
       q.push(n+m+1);
       while(!q.empty())
       {
          int top=q.front();q.pop();
          from[top]=1;
          for(int i=head[top];i!=-1;i=edge[i].next)
          {
              if(i&1)continue;
              int v=edge[i].to;
              if(!from[v]&&edge[i].cap>edge[i].flow)q.push(v);
          }
       }
       while(!q.empty())q.pop();
       q.push(0);
       while(!q.empty())
       {
           int top=q.front();q.pop();
           to[top]=1;
           for(int i=head[top];i!=-1;i=edge[i].next)
           {
               if(i%2==0)continue;
               int v=edge[i].to;
               if(!to[v]&&edge[i^1].cap-edge[i^1].flow>0)q.push(v);
           }
       }
       memset(vis,0,sizeof(vis));
       int flag=0;
       for(int i=0;i<=n+m;i++)
       {
          for(int j=head[i];j!=-1;j=edge[j].next)
          {
              if(j>=0&&j<2*l&&j%2==0&&from[i]&&to[edge[j].to]&&edge[j].cap==edge[j].flow)
                vis[j]=1,flag=1;
          }
       }
       if(flag==0){puts("");continue;}
       for(int i=0;i<2*l;i+=2)if(vis[i])
       {
           if(flag==0)printf(" ");
           else flag=0;
           printf("%d",i/2+1);
       }
       puts("");
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值