图论模板——最大流问题

Dinic算法的时间复杂度为O(n^2m)。实际运用远远小于这个上界。
特别的,Dinic算法求解二分图最大匹配的时间复杂度为O(m
sqrt(n))
最大流问题模板

#include <bits/stdc++.h>
using namespace std;
const int N = 10010,M = 200010, INF = 1e8;
int n,m,S,T;
int h[N],ne[M],e[M],f[M],idx;
int d[N],q[N],cur[N];
void add(int a,int b,int c)
{
    e[idx] = b,f[idx] = c,ne[idx] = h[a],h[a] = idx++;
    e[idx] = a,f[idx] = 0,ne[idx] = h[b],h[b] = idx++;
}
bool bfs()
{
    int hh = 0, tt = 0;
    memset(d, -1, sizeof d);
    q[0] = S, d[S] = 0, cur[S] = h[S];
    while (hh <= tt)
    {
        int t = q[hh ++ ];
        for (int i = h[t]; ~i; i = ne[i])
        {
            int ver = e[i];
            if (d[ver] == -1 && f[i])
            {
                d[ver] = d[t] + 1;
                cur[ver] = h[ver];
                if (ver == T)  return true;
                q[ ++ tt] = ver;
            }
        }
    }
    return false;
}
int find(int u,int limit)
{
    if (u==T) return limit;
    int flow = 0;
    for (int i = cur[u];~i&&flow<limit;i = ne[i])
    {
        cur[u] = i;//当前弧优化
        int ver = e[i];
        if (d[ver] == d[u]+1&&f[i])
        {
            int t = find(ver,min(f[i],limit-flow));
            if (!t) d[ver] = -1;
            f[i] -= t,f[i^1] += t,flow+=t;
        }
    }
    return flow;
}
int dinic()
{
    int r = 0,flow;
    while (bfs()) while (flow = find(S,INF)) r += flow;
    return r;
}
int main()
{
    scanf("%d%d%d%d",&n,&m,&S,&T);
    memset(h,-1,sizeof h);
    while (m--)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    printf("%d\n",dinic());
    return 0;
}

应用一:二分图匹配问题
飞行员配对方案问题 https://www.acwing.com/problem/content/2177/

int main()
{
    scanf("%d%d",&m,&n);
    S = 0,T = n+1;
    memset(h,-1,sizeof h);
    for (int i = 1;i<=m;i++) add(S,i,1);
    for (int i = m+1;i<=n;i++) add(i,T,1);
    int a,b;
    while (scanf("%d%d",&a,&b),a!=-1) add(a,b,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]);
    return 0;
}

应用二:无源汇的最大流问题
无源汇上下界可行流 https://www.acwing.com/problem/content/2190/)
注意流量守恒

int main()
{
    memset(h,-1,sizeof h);
    scanf("%d%d",&n,&m);
    S = 0,T = n+1,tot = 0;
    for (int i=0;i<m;i++)
    {
        int a,b,c,d;
        scanf("%d%d%d%d",&a,&b,&c,&d);
        add(a,b,c,d);
        A[a] -= c,A[b] += c;//出去c,进来c
    }
    for (int i=1;i<=n;i++)
    {
        if (A[i]>0) add(S,i,0,A[i]),tot += A[i];//如果进来大于0
        else if (A[i]<0) add(i,T,0,-A[i]);//如果出去
    }
    if (dinic()!=tot) printf("NO\n");//如果最低的流量都不行
    else 
    {
        printf("YES\n");
        for (int i=0;i<m*2;i+=2)
            printf("%d\n",f[i^1]+l[i]);//输出方案
    }
    return 0;
}

应用三:有源汇的最大流问题
有源汇上下界最大流 https://www.acwing.com/problem/content/2191/
先链接一个t到s的边,看是否能满足所有边的最小下限
如果满足再做一次dinic()

int main()
{
    memset(h,-1,sizeof h);
    int s,t,tot = 0;
    scanf("%d%d%d%d",&n,&m,&s,&t);
    S = 0,T = n+1;
    while (m--)
    {
        int a,b,c,d;
        scanf("%d%d%d%d",&a,&b,&c,&d);
        add(a,b,d-c);
        A[a] -= c,A[b] += c;
    }
    for (int i=1;i<=n;i++)
        if (A[i]>0) add(S,i,A[i]),tot += A[i];
        else if (A[i]<0) add(i,T,-A[i]);
    add(t,s,INF);
    if (dinic()<tot) puts("No Solution");
    else 
    {
        int res = f[idx-1];
        f[idx-1] = f[idx-2] = 0;
        S = s,T = t;
        printf("%d",res+dinic());
    }
}

应用四:有源汇的最小流问题
有源汇上下界最小流 https://www.acwing.com/problem/content/2192/
加t到s的边,然后dinic,然后再反向dinic

int main()
{
    memset(h,-1,sizeof h);
    int s,t,tot = 0;
    scanf("%d%d%d%d",&n,&m,&s,&t);
    S = 0,T = n+1;
    while (m--)
    {
        int a,b,c,d;
        scanf("%d%d%d%d",&a,&b,&c,&d);
        add(a,b,d-c);
        A[a] -= c,A[b] += c;
    }
    for (int i=1;i<=n;i++)
        if (A[i]>0) add(S,i,A[i]),tot += A[i];
        else if (A[i]<0) add(i,T,-A[i]);
    add(t,s,INF);
    if (dinic()<tot) puts("No Solution");
    else 
    {
        int res = f[idx-1];
        f[idx-1] = f[idx-2] = 0;
        S = t,T = s;
        printf("%d",res-dinic());
    }
}

应用五:多源汇的最大流问题
多源汇最大流 https://www.acwing.com/problem/content/2236/

int main()
{
    memset(h,-1,sizeof h);
    int ss,tt;
    scanf("%d%d%d%d",&n,&m,&ss,&tt);
    S = 0,T = n+1;
    while (ss--)
    {
        int x;scanf("%d",&x);
        add(S,x,INF);
    }
    while (tt--)
    {
        int x;scanf("%d",&x);
        add(x,T,INF);
    }
    while (m--)
    {
        int a,b,c;scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    printf("%d\n",dinic());
}

应用六:关键边
伊基的故事 I - 道路重建 https://www.acwing.com/problem/content/2238/
dinic后从S和T遍历

void dfs(int u, bool st[], int t)
{
    st[u] = true;
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = i ^ t, ver = e[i];
        if (f[j] && !st[ver])
            dfs(ver, st, t);
    }
}
int main()
{
    memset(h,-1,sizeof h);
    scanf("%d%d",&n,&m);
    for (int i = 0;i<m;i++)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    S = 0,T = n-1;
    dinic();
    dfs(S, vis_s, 0);
    dfs(T, vis_t, 1);

    int res = 0;
    for (int i = 0; i < m * 2; i += 2)
        if (!f[i] && vis_s[e[i ^ 1]] && vis_t[e[i]])
            res ++ ;

    printf("%d\n", res);
    return 0;
}

应用七:有边大小使用限制的最大流判定
秘密挤奶机 https://www.acwing.com/problem/content/2279/

bool check(int x)
{
    for (int i=0;i<idx;i++) 
        if (w[i]>x) f[i] = 0;
        else f[i] = 1;
    return dinic()>=k;
}
int main()
{
    memset(h,-1,sizeof h);
    scanf("%d%d%d",&n,&m,&k);
    S = 1,T = n;
    for (int i = 0;i<m;i++)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    int l = 1,r = 1e6;
    while (l<r)
    {
        int mid = l+r>>1;
        if (check(mid)) r = mid;
        else l = mid+1;
    }
    printf("%d\n",r);
    return 0;
}

应用八:拆点
餐饮 https://www.acwing.com/problem/content/2242/

int main()
{
    memset(h,-1,sizeof h);
    scanf("%d%d%d",&n,&m,&k);
    S = 0,T = 2*n+m+k+1;
    for (int i = 2*n+1;i<=2*n+m;i++) add(S,i,1);
    for (int i = 1;i<=n;i++) add(i,i+n,1);
    for (int i = 2*n+m+1;i<=2*n+m+k;i++) add(i,T,1);
    for (int i = 1;i<=n;i++)
    {
        int a,b,x;
        scanf("%d%d",&a,&b);
        for (int j = 0;j<a;j++)
        {
            scanf("%d",&x);
            add(2*n+x,i,1);
        }
        for (int j = 0;j<b;j++)
        {
            scanf("%d",&x);
            add(n+i,2*n+m+x,1);
        }
    }
    printf("%d\n",dinic());
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值