网络最大流建模


最大流问题一般求集合最优解,要求我们可以建立原问题和网络流集合的一一对应关系

在后续的方案输出中,一定要记得那些是原图的边,那些是残余网络中的边,针对问题进行不同的分析

对于所有可行解的集合P:

  • 1,对于流网络的所有可行流的集合,对于可行解中的一个解,有且只有一个可行流与之符合

  • 2,对于流网络中的任何一个可行流都能对应一个可行解

则可行解中的最大值等于可行流中的最大流

在模拟退火中我们也接触了集合最优,这类问题的证伪手段一般是局部左右不等于全局最优,是 OI 中及其常见的一种题型

一,二分图建模

1,最大匹配

从源点S到汇点T建立:

  • 从源点S流向左集合 S 的容量为 1 的边
  • 左集合到右集合的容量为 1 的边
  • 从右集合到汇点T的容量为1的边
  • 对于此流网络的最大流便是最大二分匹配

注意对分属于(二分图的)不同点集的点进行编号,使其编号不一

具体方案

  • 找到真正二分图的匹配边,其实就是编号为偶数的边(存储边的成对变换)
  • 看一下这边的残余容量是否为空,为空即为匹配成功,使用了这条边
int main()
{
    scanf("%d%d", &m, &n);
    memset(h, -1, sizeof h);
    s=0; t=n+1;
    int a, b;
    while (cin>>a>>b,a!=-1&&b!=-1)add(a, b, 1);
    rep(i,1,m)add(s,i,1);
    rep(i,m+1,t-1) add(i,t,1);
    printf("%d\n", Dinic());
    for (int i = 0; i < idx; i += 2)
        if (e[i] > m && e[i] <= n && !f[i])
            printf("%d %d\n", e[i ^ 1], e[i]);
}

2,多重匹配

从源点S到汇点T建立:

  • (匹配上限)从源点S流向左集合 S 的容量为 第一个集合同种类型点的数目 的边
  • (匹配方式:一一匹配)左集合到右集合的容量为 1 的边
  • (匹配上限)从右集合到汇点T的容量为1的边
  • 对于此流网络的最大流便是最大二分匹配
int main()
{
    cin>>m>>n;
    memset(h, -1, sizeof h);
    s=0; t=n+1+m;
    int tot= 0;
    rep(i,1,m)
    {
        int x; cin>>x; tot+=x;
        add(s,i,x);
    }

    rep(i,m+1,t-1)
    {
        int c;
        scanf("%d", &c);
        add(i, t, c);
    }

    rep(i,1,m)
        rep(j,m+1,t-1)
            add(i, j, 1);

    if (Dinic() != tot) puts("0");

    else
    {
        puts("1");
        rep(i,1,m)
        {
            for (int j = h[i]; ~j; j = ne[j])
                if (e[j] > m && e[j] <= m + n && !f[j])
                    printf("%d ", e[j] - m);
            puts("");
        }
    }
    return 0;
}

二,有上下界网络流

1,无源汇上下界可行流

描述

给定一个包含 n n n 个点 m m m 条边的有向图,每条边都有一个流量下界和流量上界。

求一种可行方案使得在所有点满足流量平衡条件的前提下,所有边满足流量限制。

思路

  • 图函数转化,虚拟源汇
  • [ G , f ] [G,f] [G,f] 是原来的流网络和一个可行流网络,其中可行流 f f f 需要同时满足下界和上界
  • [ G ′ , f ′ ] [G',f'] [G,f] 是一个构造流网络,这个图没有下界,只有上界,只要借助代数特征在两边同时减去下界即可
  • 熟知: [ G ′ , f ′ ] [G',f'] [G,f] 的可行最大流 f ′ f' f [ G , f ] [G,f] [G,f] 中的 可行流 f ′ f' f 一一对应
  • 为了保证流量守恒,只须对每个点统计下界在出入抵消后的偏移量,多退少补,哪边大说明那边减的多,从源汇补偿即可

实现

  • A 数组记录了哪边多,l 数组记录了每条边的下界(多用于方案输出中)
  • S,T 是虚拟源汇
  • 特殊的建边方案
void add(int a, int b, int c,int d)
{
    l[idx] = c; e[idx] = b, f[idx] = d-c, ne[idx] = h[a], h[a] = idx ++ ;
    e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++ ;
}
int main()
{
    memset(h, -1, sizeof h);
    read(n); read(m);
    rep(i,1,m)
    {
        int a,b,c,d;
        read(a); read(b); read(c); read(d);
        add(a,b,c,d);
        A[a]-=c;
        A[b]+=c;
    }

    S = 0;
    T = n+1;
    int tot = 0;
    rep(i,1,n)
    {
        if(A[i]>0) add(S,i,0,A[i]),tot+=A[i];
        else if(A[i]<0) add(i,T,0,-A[i]);
    }

    if(Dinic()==tot)
    {
        puts("YES");
        for(int i = 0;i <= m * 2-2; i+=2)
        {
            wri(f[i^1]+l[i]);
            puts("");
        }
    }
    else puts("NO");
    return 0;
}

2,有源汇上下界最大流

  • 先联通实际源汇,并对联通路实际源汇的新图里跑无源汇上下界最大流
  • 删去实际源汇的边及其流量
  • 在跑完的残留网络中求解实际源汇的最大流即为答案
int main()
{
    memset(h, -1, sizeof h);
    read(n); read(m);
    read(s); read(t);

    rep(i,1,m)
    {
        int a,b,c,d;
        read(a); read(b); read(c); read(d);
        add(a,b,c,d);
        A[a]-=c;
        A[b]+=c;
    }

    S = 0;
    T = n+1;
    add(t,s,0,INF);
    int tot = 0;
    rep(i,1,n)
    {
        if(A[i]>0) add(S,i,0,A[i]),tot+=A[i];
        else if(A[i]<0) add(i,T,0,-A[i]);
    }

    if(Dinic()==tot)
    {
        S= s; T = t;
        f[idx-1]=f[idx-2]= 0;
        wri(Dinic());

    }
    else puts("No Solution");

    return 0;

}

3,有源汇上下界最小流

之前已经证明过
∣ f 0 ∣ + ∣ f ′ ( s → t ) ∣ = f ( s → t ) |f_0|+|f′(s→t)|=f(s→t) f0+f(st)=f(st)

所以

  • f m a x ( s → t ) = ∣ f 0 ∣ + m a x f ′ ( s → t ) fmax(s→t)=|f_0|+maxf′(s→t) fmax(st)=f0+maxf(st)

  • f m i n ( s → t ) = ∣ f 0 ∣ + m i n f ′ ( s → t ) fmin(s→t)=|f_0|+minf′(s→t) fmin(st)=f0+minf(st)

  • 由于 f ′ ( s → t ) = − f ′ ( t → s ) f′(s→t)=−f′(t→s) f(st)=f(ts),所以 m i n f ′ ( s → t ) = − m a x f ′ ( t → s ) minf′(s→t)=−maxf′(t→s) minf(st)=maxf(ts)

  • 于是我们求出浸润流 f 0 f_0 f0 之后,在残量网络中求一遍 t → s t→s ts的最大流

  • a n s = f 0 − d i n i c ( t , s ) ans=f_0−dinic(t,s) ans=f0dinic(t,s)

int main()
{
    memset(h, -1, sizeof h);
    read(n); read(m);
    read(s); read(t);

    rep(i,1,m)
    {
        int a,b,c,d;
        read(a); read(b); read(c); read(d);
        add(a,b,c,d);
        A[a]-=c;
        A[b]+=c;
    }

    S = 0;
    T = n+1;

    int tot = 0;
    rep(i,1,n)
    {
        if(A[i]>0) add(S,i,0,A[i]),tot+=A[i];
        else if(A[i]<0) add(i,T,0,-A[i]);
    }

    add(t,s,0,INF);

    if(Dinic()>=tot)
    {
        S= t; T = s;
        tot = f[idx-1];
        f[idx-1]=f[idx-2]= 0;
        wri(tot - Dinic());

    }
    else puts("No Solution");

    return 0;
}

三,多源汇(参考多重匹配)

int main()
{
    int s_num,t_num;
    memset(h, -1, sizeof h);
    read(n); read(m);
    read(s_num); read(t_num);

    S = 0; T = n+1;
    
    rep(i,1,s_num)
    { int id; read(id); add(S,id,INF); }

    rep(i,1,t_num)
    { int id; read(id); add(id,T,INF); }
    
    rep(i,1,m)
    {
        int a,b,c;
        read(a); read(b); read(c);
        add(a,b,c);
    }
    printf("%d",Dinic());
    return 0;
}

四,最大流关键边

  • 在最大流中拓宽一条边的了、容量可以使得整个最大流增大
  • 通俗来讲就是瓶颈边
  • 注意dfs联通性时不需要反向边,否则就一直在指自己

思路

  • 判断是否是关键边,在于判断增加当前边容量后,是否存在从s->t的增广路。

  • 对于 E ( u , v ) E(u,v) E(u,v)来说,只须判断从 s − > u s->u s>u和从 v − > t v->t v>t是否都存在流量大于 0 0 0的路

  • 实现上只需要源汇同时走有容量的边,直到无法扩张,并枚举每条边判断是否满容且向两边能到源汇
    在这里插入图片描述

void dfs_find(int u,bool st[],int op)
{
    st[u] = 1 ;
    mrep(i,u)
    {
        int v = e[i];
        if(f[i^op] && !st[v])dfs_find(v,st,op);
    }
}
int main()
{
    memset(h, -1, sizeof h);
    read(n); read(m);
    S = 0;
    T = n-1;
    while (m--)
    {
        int a,b,c;
        read(a); read(b); read(c);
        add(a,b,c);
    }

    Dinic();
    dfs_find(S,vis_s,0);
    dfs_find(T,vis_t,1);

    int res = 0;
    for(int i= 0;i <=idx; i+= 2)
    {
        if(f[i]==0 && vis_s[e[i^1]] && vis_t[e[i]])res++;
    }
    wri(res);
    return 0;
}

五,最大流判定

无向图网络流+二分答案

在这里插入图片描述

  • 在判定性(连通性)的存边时,可以思考辅助 W 数组,表示容量上
    限,然后在二分的时候根据lim 随时修改残余网络的01情况

  • 所谓的两个方向建边其实就是把一条边f的双向值全部修改为容量即可(残余网络中为容量为0表示这条边不存在)

  • 无向图,要求每条边最多只能走一次,在建流网络的时候 ( u , v ) (u,v) (u,v)正向走一次, ( v , u ) (v,u) (v,u) 反向又走了一次,等价于没有流,把相应的边删去即可

void add (int a,int b,int c)
{
    ne[idx]=h[a]; e[idx]=b; f[idx]=0; w[idx]=c; h[a]=idx++;
    ne[idx]=h[b]; e[idx]=a; f[idx]=0; w[idx]=c; h[b]=idx++;
}
bool check(int lim)
{
    rep(i,0,idx-1)
    {
        if(w[i]<=lim) f[i] = 1;
        else f[i] = 0;
    }
    return Dinic() >= tar;
}
int main()
{
    Mset(h,-1);
    read(n); read(m); read(tar);
    S = 1;
    T = n;
    int l = 1;
    int r = 1e6;

    rep(i,1,m)
    {
        int a,b,c;
        read(a); read(b); read(c);
        add(a,b,c);
    }

    while (l<r)
    {
        int mid = (l+r) >> 1;
        if(check(mid)) r=mid;
        else l =mid+1;
    }

    wri(r);
    return 0;
}

六,拆点

  • 对于在点上或者经过某个点的时候有限制的时候
  • 将单独一个点拆成入点和出点,入点和出点之间的连边设置上限即可
  • 形式上:add (i,i+n,lim);
  • 题面上:每个点只能匹配一个XX

七,实践和题型总结

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流苏贺风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值