学习笔记:最大流

概念

最大流,网络流中的一个问题。
网络流即指一个源点 s s s有无尽的水源,经过一些最多流 c i c_i ci的水的管道,到达汇点 t t t的图。
流量:经过某个管道的水的总量或者到达某个点的水的总量。
容量:即指上面所述的 c i c_i ci,管道最大能流的流量。
一个可行流就是满足以下两点的一个流:

  1. 流量守恒:流到点u多少就从点u流出去多少。(源点和汇点除外)
  2. 容量限制:每一条边的流量不大于该边的容量。

最大流,故名思意,就是可行流中到达 t t t(从 s s s流出去)的流量最大的一个。
退流:把流过的流量又流回去,相当于“退货”。
残留网络:指加上可以退流的边的图。退流的边就是反向边。反向边的容量等于原来的边已用的容量,原来的边就是总容量减去用去的容量。反向边为的是给一个后悔的机会,正向边方便计算增广路。
增广路:指能增加最大流量的一条水流的路径。
分层图:把图分成很多层的图。层数就是到 s s s的最短距离。

算法

EK算法

方法

每次找一条增广路,答案该增广路的流量,并修改残留网络,原边减去增广路的流量,反向边加上增广路的流量。
时间复杂度 O ( n m 2 ) O(nm^2) O(nm2)

例题

AcWing 2171

板子题,直接把模板贴上去即可

#include<bits/stdc++.h>
using namespace std;
const int NN=1004,MM=20004;
int n,m,s,t,w[MM],e[MM],head[NN],ne[MM],d[NN],pre[NN],idx=-1;
bool st[NN];
void add(int u,int v,int c)
{
    
    
    e[++idx]=v;
    ne[idx]=head[u];
    w[idx]=c;
    head[u]=idx;
    e[++idx]=u;
    ne[idx]=head[v];
    head[v]=idx;
}
bool bfs()
{
    
    
    queue<int>q;
    q.push(s);
    memset(st,false,sizeof(st));
    st[s]=true;
    d[s]=1e9;
    while(q.size())
    {
    
    
        int f=q.front();
        q.pop();
        for(int i=head[f];~i;i=ne[i])
        {
    
    
            int v=e[i];
            if(!st[v]&&w[i])
            {
    
    
                st[v]=true;
                d[v]=min(d[f],w[i]);
                pre[v]=i;
                if(v==t)
                    return true;
                q.push(v);
            }
        }
    }
    return false;
}
int EK()
{
    
    
    int res=0;
    while(bfs())
    {
    
    
        res+=d[t];
        for(int i=t;i!=s;i=e[pre[i]^1])
        {
    
    
            w[pre[i]]-=d[t];
            w[pre[i]^1]+=d[t];
        }
    }
    return res;
}
int main()
{
    
    
    memset(head,-1,sizeof(head));
    scanf("%d%d%d%d",&n,&m,&s,&t);
    for(int i=1;i<=m;i++)
    {
    
    
        int u,v,c;
        scanf("%d%d%d",&u,&v,&c);
        add(u,v,c);
    }
    printf("%d",EK());
    return 0;
}

dinic算法

方法

其实是 E K EK EK算法的一种优化,每次取完能取的一切增广路。因为图中有可能有环导致死循环,则建一个分层图,只能一层一层地走,就不会往回走而死循环了。考虑如何取完所有能取的增广路,设定一个从源点流向 u u u的最大流量,然后 u u u将流量分给所有的 v v v。注意分给 v v v的流量不能超过 u → v u\to v uv的边的容量。
时间复杂度 O ( n 2 m ) O(n^2m) O(n2m)

优化

d i n i c dinic dinic算法仍然可以优化。

  1. 当前弧优化:每次取完了前面的一些路径的流量,则以后没有必要再搜索了。加了该优化必须加优化 2 2 2,否则新的邻接表的表头就会一直被更新,容易 T L E TLE TLE
  2. 可行性优化:如果给 u u u分的流量等于目前 u u u分给所有儿子的流量,那么再枚举也没有必要了。因为前面分给 u u u的流量已经全部分完了。
  3. 重复优化:如果之前算过某个路径不行,那么以后在该分层图上就不用计算该路径了。

例题

AcWing 2172

板子题,直接套板子

#include<bits/stdc++.h>
using namespace std;
const int NN=10004,MM=200004;
int n,m,s,t,h[NN],ne[MM],e[MM],c[MM],idx=-1,head[NN],d[NN];
void add(int u,int v,int w)
{
    
    
    e[++idx]=v;
    c[idx]=w;
    ne[idx]=h[u];
    h[u]=idx;
    e[++idx]=u;
    ne[idx]=h[v];
    h[v]=idx;
}
bool bfs()
{
    
    
    queue<int>q;
    memset(d,-1,sizeof(d));
    q.push(s);
    d[s]=0;
    head[s]=h[s];
    while(q.size())
    {
    
    
        int f=q.front();
        q.pop();
        for(int i=h[f];~i;i=ne[i])
        {
    
    
            int v=e[i];
            if(d[v]==-1&&c[i])
            {
    
    
                d[v]=d[f]+1;
                head[v]=h[v];
                if(v==t)
                    return true;
                q.push(v);
            }
        }
    }
    return false;
}
int find(int u,int sum)
{
    
    
    if(u==t)
        return sum;
    int res=0;
    for(int i=head[u];~i&&res<sum;i=ne[i])
    {
    
    
        head[u]=i;
        int v=e[i];
        if(d[v]==d[u]+1&&c[i])
        {
    
    
            int temp=find(v,min(c[i],sum-res));
            if(!temp)
                d[v]=-1;
            c[i]-=temp;
            c[i^1]+=temp;
            res+=temp;
        }
    }
    return res;
}
int dinic()
{
    
    
    int res=0,sum;
    while(bfs())
        while(sum=find(s,1e9))
            res+=sum;
    return res;
}
int main()
{
    
    
    scanf("%d%d%d%d",&n,&m,&s,&t);
    memset(h,-1,sizeof(h));
    for(int i=1;i<=m;i++)
    {
    
    
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);
    }
    printf("%d",dinic());
    return 0;
}

最大流解二分图

概念

二分图就是把无向图的点集分成两个部分,要求每条边都跨过两个点集的一个图。这里会介绍如何用网络流解决二分图问题。

方法

其实就是左边的点集的每一个点,可以选择一个右边没有被选择过的点,或者不选。相当于给左边一个流量,让它选择流到右边哪个点。到了右边,因为不能有多个左边的点选择右边的同一个点,所以相当于每个右边的点只能选一个左边的点帮你把水流到终点,则向 t t t连一条容量为 1 1 1的边。

例题

AcWing 2175

就是二分图,但是匈牙利算法时间不够,网络流可以更快。题目要求方案,本题中横跨左右且流了水的边,就说明匹配了该边连接的左右两个点。本题中流了水的边就是残留网络中正向边容量为 0 0 0或者反向边容量为 1 1 1的边。

#include<bits/stdc++.h>
using namespace std;
const int NN=10004,MM=200004;
int s,t,h[NN],ne[MM],e[MM],c[MM],idx=-1,head[NN],d[NN];
void add(int u,int v,int w)
{
    
    
    e[++idx]=v;
    c[idx]=w;
    ne[idx]=h[u];
    h[u]=idx;
    e[++idx]=u;
    ne[idx]=h[v];
    h[v]=idx;
}
bool bfs()
{
    
    
    queue<int>q;
    memset(d,-1,sizeof(d));
    q.push(s);
    d[s]=0;
    head[s]=h[s];
    while(q.size())
    {
    
    
        int f=q.front();
        q.pop();
        for(int i=h[f];~i;i=ne[i])
        {
    
    
            int v=e[i];
            if(d[v]==-1&&c[i])
            {
    
    
                d[v]=d[f]+1;
                head[v]=h[v];
                if(v==t)
                    return true;
                q.push(v);
            }
        }
    }
    return false;
}
int find(int u,int sum)
{
    
    
    if(u==t)
        return sum;
    int res=0;
    for(int i=head[u];~i&&res<sum;i=ne[i])
    {
    
    
        head[u]=i;
        int v=e[i];
        if(d[v]==d[u]+1&&c[i])
        {
    
    
            int temp=find(v,min(c[i],sum-res));
            if(!temp)
                d[v]=-1;
            c[i]-=temp;
            c[i^1]+=temp;
            res+=temp;
        }
    }
    return res;
}
int dinic()
{
    
    
    int res=0,sum;
    while(bfs())
        while(sum=find(s,1e9))
            res+=sum;
    return res;
}
int main()
{
    
    
    int m,n;
    scanf("%d%d",&m,&n);
    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 u,v;
    while(scanf("%d%d",&u,&v)!=EOF)
        add(u,v,1);
    printf("%d\n",dinic());
    for(int i=0;i<idx;i+=2)
        if(e[i]>m&&e[i]<=n&&!c[i])
            printf("%d %d\n",e[i^1],e[i]);
    return 0;
}

AcWing 2179

这个题就相当于左边和右边的点可以使用多次,则把 s s s到左边的边和右边到 t t t的边的容量限制放开就行了。一个单位不在同一个桌,那么左边的点就只能向右边某一个点流一个。

扫描二维码关注公众号,回复: 12011983 查看本文章
#include<bits/stdc++.h>
using namespace std;
const int NN=1004,MM=2000004;
int s,t,h[NN],ne[MM],e[MM],c[MM],idx=-1,head[NN],d[NN];
void add(int u,int v,int w)
{
    
    
    e[++idx]=v;
    c[idx]=w;
    ne[idx]=h[u];
    h[u]=idx;
    e[++idx]=u;
    ne[idx]=h[v];
    h[v]=idx;
}
bool bfs()
{
    
    
    queue<int>q;
    memset(d,-1,sizeof(d));
    q.push(s);
    d[s]=0;
    head[s]=h[s];
    while(q.size())
    {
    
    
        int f=q.front();
        q.pop();
        for(int i=h[f];~i;i=ne[i])
        {
    
    
            int v=e[i];
            if(d[v]==-1&&c[i])
            {
    
    
                d[v]=d[f]+1;
                head[v]=h[v];
                if(v==t)
                    return true;
                q.push(v);
            }
        }
    }
    return false;
}
int find(int u,int sum)
{
    
    
    if(u==t)
        return sum;
    int res=0;
    for(int i=head[u];~i&&res<sum;i=ne[i])
    {
    
    
        head[u]=i;
        int v=e[i];
        if(d[v]==d[u]+1&&c[i])
        {
    
    
            int temp=find(v,min(c[i],sum-res));
            if(!temp)
                d[v]=-1;
            c[i]-=temp;
            c[i^1]+=temp;
            res+=temp;
        }
    }
    return res;
}
int dinic()
{
    
    
    int res=0,sum;
    while(bfs())
        while(sum=find(s,1e9))
            res+=sum;
    return res;
}
int main()
{
    
    
    int m,n;
    scanf("%d%d",&m,&n);
    t=m+n+1;
    memset(h,-1,sizeof(h));
    int sum=0;
    for(int i=1;i<=m;i++)
    {
    
    
        int res;
        scanf("%d",&res);
        add(s,i,res);
        sum+=res;
    }
    for(int i=1;i<=n;i++)
    {
    
    
        int res;
        scanf("%d",&res);
        add(i+m,t,res);
    }
    for(int i=1;i<=m;i++)
        for(int j=1;j<=n;j++)
            add(i,j+m,1);
    if(dinic()!=sum)
        puts("0");
    else
    {
    
    
        puts("1");
        for(int i=1;i<=m;i++)
        {
    
    
            for(int j=h[i];~j;j=ne[j])
                if(e[j]>m&&e[j]<=n+m&&!c[j])
                    printf("%d ",e[j]-m);
            puts("");
        }
    }
    return 0;
}

上下界可行流

概念

图中每条管道加上了容量下界。

方法

上界减去下界,最后每一条边的流量加上下界的流量就是答案。但是这样 G ′ G' G流量守恒,但有可能原图 G G G流量不守恒。那么如果原图流入的多,就从 s s s向该点补充原图到新图少了的流量。反之向 t t t把多了的流出去。只有满流(这样才流量守恒)才有解。

例题

AcWing 2188

这个题按刚才说的方法做就行了。

#include<bits/stdc++.h>
using namespace std;
const int NN=204,MM=20804;
int h[NN],ne[MM],e[MM],c[MM],d[NN],head[NN],x[NN],down[MM],idx=-1,s,t;
void add(int u,int v,int l,int w)
{
    
    
    e[++idx]=v;
    ne[idx]=h[u];
    c[idx]=w;
    down[idx]=l;
    h[u]=idx;
    e[++idx]=u;
    ne[idx]=h[v];
    h[v]=idx;
}
bool bfs()
{
    
    
    queue<int>q;
    memset(d,-1,sizeof(d));
    d[s]=0;
    q.push(s);
    head[s]=h[s];
    while(q.size())
    {
    
    
        int f=q.front();
        q.pop();
        for(int i=h[f];~i;i=ne[i])
        {
    
    
            int v=e[i];
            if(d[v]==-1&&c[i])
            {
    
    
                d[v]=d[f]+1;
                head[v]=h[v];
                if(v==t)
                    return true;
                q.push(v);
            }
        }
    }
    return false;
}
int find(int u,int minn)
{
    
    
    if(u==t)
        return minn;
    int res=0;
    for(int i=head[u];~i&&res<minn;i=ne[i])
    {
    
    
        head[u]=i;
        int v=e[i];
        if(d[v]==d[u]+1&&c[i])
        {
    
    
            int temp=find(v,min(c[i],minn-res));
            if(!temp)
                d[v]=-1;
            c[i]-=temp;
            c[i^1]+=temp;
            res+=temp;
        }
    }
    return res;
}
int dinic()
{
    
    
    int res=0,sum;
    while(bfs())
        while(sum=find(s,1e9))
            res+=sum;
    return res;
}
int main()
{
    
    
    int n,m,res=0;
    scanf("%d%d",&n,&m);
    t=n+1;
    memset(h,-1,sizeof(h));
    for(int i=1;i<=m;i++)
    {
    
    
        int u,v,a,b;
        scanf("%d%d%d%d",&u,&v,&a,&b);
        add(u,v,a,b-a);
        x[u]-=a;
        x[v]+=a;
    }
    for(int i=1;i<=n;i++)
        if(x[i]>0)
        {
    
    
            add(s,i,0,x[i]);
            res+=x[i];
        }
        else if(x[i]<0)
            add(i,t,0,-x[i]);
    if(dinic()!=res)
        printf("NO");
    else
    {
    
    
        puts("YES");
        for(int i=0;i<m*2;i+=2)
            printf("%d\n",c[i^1]+down[i]);
    }
    return 0;
}

AcWing 2189

这个题有源汇,那么可以先把 G ′ G' G算出来,把不守恒的流量补齐了再计算有源汇的图。但是原图 G G G有源点和汇点,但是它们变成新图就不能无限出和无限入了。保证变成无源汇之后流量守恒,那么就得让 t t t能无限出, s s s能无限入,则连一条 t t t s s s的边就行了。如果是满流,这时就补充了缺失的流量,保证了流量守恒。再把 s s s变成 S S S t t t变成 T T T,把原图 t t t s s s的边删除(因为这条边是为了流量守恒的)即可。两个图的流量相加就是答案。

#include<bits/stdc++.h>
using namespace std;
const int NN=204,MM=20804;
int h[NN],ne[MM],e[MM],c[MM],d[NN],head[NN],x[NN],idx=-1,S,T;
void add(int u,int v,int w)
{
    
    
    e[++idx]=v;
    ne[idx]=h[u];
    c[idx]=w;
    h[u]=idx;
    e[++idx]=u;
    ne[idx]=h[v];
    h[v]=idx;
}
bool bfs()
{
    
    
    queue<int>q;
    memset(d,-1,sizeof(d));
    d[S]=0;
    q.push(S);
    head[S]=h[S];
    while(q.size())
    {
    
    
        int f=q.front();
        q.pop();
        for(int i=h[f];~i;i=ne[i])
        {
    
    
            int v=e[i];
            if(d[v]==-1&&c[i])
            {
    
    
                d[v]=d[f]+1;
                head[v]=h[v];
                if(v==T)
                    return true;
                q.push(v);
            }
        }
    }
    return false;
}
int find(int u,int minn)
{
    
    
    if(u==T)
        return minn;
    int res=0;
    for(int i=head[u];~i&&res<minn;i=ne[i])
    {
    
    
        head[u]=i;
        int v=e[i];
        if(d[v]==d[u]+1&&c[i])
        {
    
    
            int temp=find(v,min(c[i],minn-res));
            if(!temp)
                d[v]=-1;
            c[i]-=temp;
            c[i^1]+=temp;
            res+=temp;
        }
    }
    return res;
}
int dinic()
{
    
    
    int res=0,sum;
    while(bfs())
        while(sum=find(S,1e9))
            res+=sum;
    return res;
}
int main()
{
    
    
    int n,m,s,t,res=0;
    scanf("%d%d%d%d",&n,&m,&s,&t);
    T=n+1;
    memset(h,-1,sizeof(h));
    for(int i=1;i<=m;i++)
    {
    
    
        int u,v,a,b;
        scanf("%d%d%d%d",&u,&v,&a,&b);
        add(u,v,b-a);
        x[u]-=a;
        x[v]+=a;
    }
    for(int i=1;i<=n;i++)
        if(x[i]>0)
        {
    
    
            add(S,i,x[i]);
            res+=x[i];
        }
        else if(x[i]<0)
            add(i,T,-x[i]);
    add(t,s,1e9);
    if(dinic()!=res)
        printf("No Solution");
    else
    {
    
    
        int res=c[idx];
        c[idx]=c[idx-1]=0;
        S=s;
        T=t;
        printf("%d",res+dinic());
    }
    return 0;
}

AcWing 2190

这个题是求最小流,那么从跑完 G ′ G' G后,跑 G ′ ′ G'' G时可以跑退流的边,把前面补完流量的最大流多补的退回去。退得越多原图 G G G流量就越小,所以可以在退流的边上跑最大流。最后原图的流量减去退回去的流量就是答案

#include<bits/stdc++.h>
using namespace std;
const int NN=50007,MM=(NN+125003)*2;
int h[NN],ne[MM],e[MM],c[MM],d[NN],head[NN],x[NN],idx=-1,S,T;
void add(int u,int v,int w)
{
    
    
    e[++idx]=v;
    ne[idx]=h[u];
    c[idx]=w;
    h[u]=idx;
    e[++idx]=u;
    ne[idx]=h[v];
    h[v]=idx;
}
bool bfs()
{
    
    
    queue<int>q;
    memset(d,-1,sizeof(d));
    d[S]=0;
    q.push(S);
    head[S]=h[S];
    while(q.size())
    {
    
    
        int f=q.front();
        q.pop();
        for(int i=h[f];~i;i=ne[i])
        {
    
    
            int v=e[i];
            if(d[v]==-1&&c[i])
            {
    
    
                d[v]=d[f]+1;
                head[v]=h[v];
                if(v==T)
                    return true;
                q.push(v);
            }
        }
    }
    return false;
}
int find(int u,int minn)
{
    
    
    if(u==T)
        return minn;
    int res=0;
    for(int i=head[u];~i&&res<minn;i=ne[i])
    {
    
    
        head[u]=i;
        int v=e[i];
        if(d[v]==d[u]+1&&c[i])
        {
    
    
            int temp=find(v,min(c[i],minn-res));
            if(!temp)
                d[v]=-1;
            c[i]-=temp;
            c[i^1]+=temp;
            res+=temp;
        }
    }
    return res;
}
int dinic()
{
    
    
    int res=0,sum;
    while(bfs())
        while(sum=find(S,1e9))
            res+=sum;
    return res;
}
int main()
{
    
    
    int n,m,s,t,res=0;
    scanf("%d%d%d%d",&n,&m,&s,&t);
    T=n+1;
    memset(h,-1,sizeof(h));
    for(int i=1;i<=m;i++)
    {
    
    
        int u,v,a,b;
        scanf("%d%d%d%d",&u,&v,&a,&b);
        add(u,v,b-a);
        x[u]-=a;
        x[v]+=a;
    }
    for(int i=1;i<=n;i++)
        if(x[i]>0)
        {
    
    
            add(S,i,x[i]);
            res+=x[i];
        }
        else if(x[i]<0)
            add(i,T,-x[i]);
    add(t,s,1e9);
    if(dinic()!=res)
        printf("No Solution");
    else
    {
    
    
        int res=c[idx];
        c[idx]=c[idx-1]=0;
        S=t;
        T=s;
        printf("%d",res-dinic());
    }
    return 0;
}

多源汇最大流

概念

本模型会有多个源点和汇点。

方法

从s向每一个源点供给无限多,每个汇点可以向t流无限多流量即可。

例题

AcWing 2234

这个题按上面的方法跑一遍就能过

#include<bits/stdc++.h>
using namespace std;
const int NN=10004,MM=(1e5+NN)*2,INF=1e9;
int e[MM],ne[MM],c[MM],h[NN],idx=-1,head[NN],d[NN],S,T;
void add(int u,int v,int w)
{
    
    
    e[++idx]=v;
    ne[idx]=h[u];
    c[idx]=w;
    h[u]=idx;
    e[++idx]=u;
    ne[idx]=h[v];
    h[v]=idx;
}
bool bfs()
{
    
    
    queue<int>q;
    memset(d,-1,sizeof(d));
    q.push(S);
    d[S]=0;
    head[S]=h[S];
    while(q.size())
    {
    
    
        int f=q.front();
        q.pop();
        for(int i=h[f];~i;i=ne[i])
        {
    
    
            int v=e[i];
            if(d[v]==-1&&c[i])
            {
    
    
                d[v]=d[f]+1;
                head[v]=h[v];
                if(v==T)
                    return true;
                q.push(v);
            }
        }
    }
    return false;
}
int find(int u,int sum)
{
    
    
    if(u==T)
        return sum;
    int res=0;
    for(int i=head[u];~i&&res<sum;i=ne[i])
    {
    
    
        int v=e[i];
        head[u]=i;
        if(d[v]==d[u]+1&&c[i])
        {
    
    
            int t=find(v,min(c[i],sum-res));
            if(!t)
                d[v]=-1;
            c[i]-=t;
            c[i^1]+=t;
            res+=t;
        }
    }
    return res;
}
int dinic()
{
    
    
    int res=0,sum;
    while(bfs())
        while(sum=find(S,INF))
            res+=sum;
    return res;
}
int main()
{
    
    
    int n,m,s,t;
    scanf("%d%d%d%d",&n,&m,&s,&t);
    T=n+1;
    memset(h,-1,sizeof(h));
    for(int i=1;i<=s;i++)
    {
    
    
        int x;
        scanf("%d",&x);
        add(S,x,INF);
    }
    for(int i=1;i<=t;i++)
    {
    
    
        int x;
        scanf("%d",&x);
        add(x,T,INF);
    }
    for(int i=1;i<=m;i++)
    {
    
    
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);
    }
    printf("%d",dinic());
    return 0;
}

技巧:拆点

方法

假如某一个点只能用 n n n次或者有多种状态,那么就可以拆点解决了。如果只能用一次,那就把一个点拆成入点和出点,所有进来的边都连向入点,出去的边从出点连,入点向出点连一条容量为 n n n的边。多种状态的就按题目要求拆点。

例题

AcWing 2240

可以把奶牛放在中间,向食物和饮料连边。但是一个奶牛只能吃一套,所以把表示奶牛的点拆点解决。

#include<bits/stdc++.h>
using namespace std;
const int NN=404,MM=40604;
int h[NN],ne[MM],e[MM],c[MM],d[NN],head[NN],x[NN],idx=-1,s,t;
void add(int u,int v,int w)
{
    
    
    e[++idx]=v;
    ne[idx]=h[u];
    c[idx]=w;
    h[u]=idx;
    e[++idx]=u;
    ne[idx]=h[v];
    h[v]=idx;
}
bool bfs()
{
    
    
    queue<int>q;
    memset(d,-1,sizeof(d));
    d[s]=0;
    q.push(s);
    head[s]=h[s];
    while(q.size())
    {
    
    
        int f=q.front();
        q.pop();
        for(int i=h[f];~i;i=ne[i])
        {
    
    
            int v=e[i];
            if(d[v]==-1&&c[i])
            {
    
    
                d[v]=d[f]+1;
                head[v]=h[v];
                if(v==t)
                    return true;
                q.push(v);
            }
        }
    }
    return false;
}
int find(int u,int minn)
{
    
    
    if(u==t)
        return minn;
    int res=0;
    for(int i=head[u];~i&&res<minn;i=ne[i])
    {
    
    
        head[u]=i;
        int v=e[i];
        if(d[v]==d[u]+1&&c[i])
        {
    
    
            int temp=find(v,min(c[i],minn-res));
            if(!temp)
                d[v]=-1;
            c[i]-=temp;
            c[i^1]+=temp;
            res+=temp;
        }
    }
    return res;
}
int dinic()
{
    
    
    int res=0,sum;
    while(bfs())
        while(sum=find(s,1e9))
            res+=sum;
    return res;
}
int main()
{
    
    
    int n,f,d;
    scanf("%d%d%d",&n,&f,&d);
    t=n*2+f+d+1;
    memset(h,-1,sizeof(h));
    for(int i=1;i<=f;i++)
        add(s,n*2+i,1);
    for(int i=1;i<=d;i++)
        add(n*2+f+i,t,1);
    for(int i=1;i<=n;i++)
    {
    
    
        add(i,n+i,1);
        int a,b;
        scanf("%d%d",&a,&b);
        while(a--)
        {
    
    
            int x;
            scanf("%d",&x);
            add(n*2+x,i,1);
        }
        while(b--)
        {
    
    
            int x;
            scanf("%d",&x);
            add(i+n,n*2+f+x,1);
        }
    }
    printf("%d",dinic());
    return 0;
}

AcWing 2180

这个题第一问就是一个动态规划。第二问每个数可以从多个数转移过来且都能得到最大值,只要满足 f [ u ] = f [ v ] + 1 f[u]=f[v]+1 f[u]=f[v]+1 a [ v ] < a [ u ] a[v]<a[u] a[v]<a[u]即可,把 v v v u u u连一条容量为 1 1 1的边,相当于让 u u u选择一个最优策略组合在一起。每个点只能用一次,拆点。所有 f [ u ] f[u] f[u]等于最优值的向t连一条容量为 1 1 1的边,因为它们会做一条链的终点。所有 f [ u ] f[u] f[u]等于 1 1 1的由 s s s连一条容量为 1 1 1的边,因为它们会作为一条链的起点。第三问,把 s s s 1 1 1 1 1 1 t t t s s s n n n n n n t t t和这两个数拆的点之间边的容量放开即可。

#include<bits/stdc++.h>
using namespace std;
const int NN=1004,MM=251004;
int h[NN],ne[MM],e[MM],c[MM],d[NN],head[NN],l[NN],g[NN],idx=-1,s,t;
void add(int u,int v,int w)
{
    
    
    e[++idx]=v;
    ne[idx]=h[u];
    c[idx]=w;
    h[u]=idx;
    e[++idx]=u;
    ne[idx]=h[v];
    h[v]=idx;
}
bool bfs()
{
    
    
    queue<int>q;
    memset(d,-1,sizeof(d));
    d[s]=0;
    q.push(s);
    head[s]=h[s];
    while(q.size())
    {
    
    
        int f=q.front();
        q.pop();
        for(int i=h[f];~i;i=ne[i])
        {
    
    
            int v=e[i];
            if(d[v]==-1&&c[i])
            {
    
    
                d[v]=d[f]+1;
                head[v]=h[v];
                if(v==t)
                    return true;
                q.push(v);
            }
        }
    }
    return false;
}
int find(int u,int minn)
{
    
    
    if(u==t)
        return minn;
    int res=0;
    for(int i=head[u];~i&&res<minn;i=ne[i])
    {
    
    
        head[u]=i;
        int v=e[i];
        if(d[v]==d[u]+1&&c[i])
        {
    
    
            int temp=find(v,min(c[i],minn-res));
            if(!temp)
                d[v]=-1;
            c[i]-=temp;
            c[i^1]+=temp;
            res+=temp;
        }
    }
    return res;
}
int dinic()
{
    
    
    int res=0,sum;
    while(bfs())
        while(sum=find(s,1e9))
            res+=sum;
    return res;
}
int main()
{
    
    
    int n;
    scanf("%d",&n);
    t=n*2+1;
    memset(h,-1,sizeof(h));
    for(int i=1;i<=n;i++)
        scanf("%d",&l[i]);
    int sum=0;
    for(int i=1;i<=n;i++)
    {
    
    
        add(i,i+n,1);
        g[i]=1;
        for(int j=1;j<i;j++)
            if(l[j]<=l[i])
                g[i]=max(g[i],g[j]+1);
        for(int j=1;j<i;j++)
            if(l[j]<=l[i]&&g[j]+1==g[i])
                add(n+j,i,1);
        sum=max(sum,g[i]);
        if(g[i]==1)
            add(s,i,1);
    }
    for(int i=1;i<=n;i++)
        if(g[i]==sum)
            add(n+i,t,1);
    printf("%d\n",sum);
    if(sum==1)
    {
    
    
        printf("%d\n%d",n,n);
        return 0;
    }
    int res=dinic();
    printf("%d\n",res);
    for(int i=0;i<=idx;i+=2)
    {
    
    
        int u=e[i^1],v=e[i];
        if(u==s&&(v==1||v==n))
            c[i]=1e9;
        if((u==1+n||u==n+n)&&v==t)
            c[i]=1e9;
        if(u==1&&v==n+1)
            c[i]=1e9;
        if(u==n&&v==n+n)
            c[i]=1e9;
    }
    printf("%d",res+dinic());
    return 0;
}

AcWing 2278

这个题就是把每两条可以到达的冰连一条容量为无限的边,冰的起跳限制拆点, s s s向每一个有企鹅的冰连一条容量为企鹅个数的边,表示给你这么多企鹅,枚举每个点当 t t t

#include<bits/stdc++.h>
using namespace std;
const int NN=204,MM=20404,INF=1e9;
const double eps=1e-8;
int h[NN],ne[MM],e[MM],c[MM],d[NN],head[NN],idx,s,t;
double D;
pair<int,int>point[NN];
bool check(pair<int,int>a,pair<int,int>b)
{
    
    
    double dx=a.first-b.first,dy=a.second-b.second;
    return dx*dx+dy*dy<D*D+eps;
}
void add(int u,int v,int w)
{
    
    
    e[++idx]=v;
    ne[idx]=h[u];
    c[idx]=w;
    h[u]=idx;
    e[++idx]=u;
    ne[idx]=h[v];
    c[idx]=0;
    h[v]=idx;
}
bool bfs()
{
    
    
    queue<int>q;
    memset(d,-1,sizeof(d));
    d[s]=0;
    q.push(s);
    head[s]=h[s];
    while(q.size())
    {
    
    
        int f=q.front();
        q.pop();
        for(int i=h[f];~i;i=ne[i])
        {
    
    
            int v=e[i];
            if(d[v]==-1&&c[i])
            {
    
    
                d[v]=d[f]+1;
                head[v]=h[v];
                if(v==t)
                    return true;
                q.push(v);
            }
        }
    }
    return false;
}
int find(int u,int minn)
{
    
    
    if(u==t)
        return minn;
    int res=0;
    for(int i=head[u];~i&&res<minn;i=ne[i])
    {
    
    
        head[u]=i;
        int v=e[i];
        if(d[v]==d[u]+1&&c[i])
        {
    
    
            int temp=find(v,min(c[i],minn-res));
            if(!temp)
                d[v]=-1;
            c[i]-=temp;
            c[i^1]+=temp;
            res+=temp;
        }
    }
    return res;
}
int dinic()
{
    
    
    int res=0,sum;
    while(bfs())
        while(sum=find(s,INF))
            res+=sum;
    return res;
}
int main()
{
    
    
    int kase;
    scanf("%d",&kase);
    while(kase--)
    {
    
    
        memset(h,-1,sizeof(h));
        idx=-1;
        int n;
        scanf("%d%lf",&n,&D);
        int sum=0;
        for(int i=1;i<=n;i++)
        {
    
    
            int x,y,a,b;
            scanf("%d%d%d%d",&x,&y,&a,&b);
            point[i]={
    
    x,y};
            add(s,i,a);
            add(i,n+i,b);
            sum+=a;
        }
        for(int i=1;i<=n;i++)
            for(int j=i+1;j<=n;j++)
                if(check(point[i],point[j]))
                {
    
    
                    add(n+i,j,INF);
                    add(n+j,i,INF);
                }
        bool ok=false;
        for(int i=1;i<=n;i++)
        {
    
    
            t=i;
            for(int j=0;j<=idx;j+=2)
            {
    
    
                c[j]+=c[j^1];
                c[j^1]=0;
            }
            if(dinic()==sum)
            {
    
    
                printf("%d ",i-1);
                ok=true;
            }
        }
        if(ok)
            puts("");
        else
            puts("-1");
    }
    return 0;
}

非经典型

AcWing 2236——关键边

这个题可以先跑一遍最大流,可以发现当且仅当某一条 s s s t t t的路径上只有一条满流的边,那条唯一满流的边才需要重建。可以发现,每一条路径上至少有一条满流的路,要不然就还有增广路。那么就可以从 s s s t t t开始,标记有路径从 s s s t t t到该点且没经过满流的边的点(t走的和s走的边应当是相同的,并不是残留网络中用来后悔的反向边),再枚举每一条边,若 u u u s s s标记了, v v v t t t标记了,那么 u u u v v v一定是唯一的满流边。

#include<bits/stdc++.h>
using namespace std;
const int NN=504,MM=10004;
int h[NN],ne[MM],e[MM],c[MM],idx=-1,head[NN],d[NN],s,t;
bool vis_s[NN],vis_t[NN];
void add(int u,int v,int w)
{
    
    
    ne[++idx]=h[u];
    e[idx]=v;
    c[idx]=w;
    h[u]=idx;
    ne[++idx]=h[v];
    e[idx]=u;
    h[v]=idx;
}
bool bfs()
{
    
    
    queue<int>q;
    q.push(s);
    memset(d,-1,sizeof(d));
    head[s]=h[s];
    d[s]=0;
    while(q.size())
    {
    
    
        int f=q.front();
        q.pop();
        for(int i=h[f];~i;i=ne[i])
        {
    
    
            int v=e[i];
            if(d[v]==-1&&c[i])
            {
    
    
                head[v]=h[v];
                d[v]=d[f]+1;
                if(v==t)
                    return true;
                q.push(v);
            }
        }
    }
    return false;
}
int find(int u,int sum)
{
    
    
    if(u==t)
        return sum;
    int res=0;
    for(int i=head[u];~i&&res<sum;i=ne[i])
    {
    
    
        head[u]=i;
        int v=e[i];
        if(d[v]==d[u]+1&&c[i])
        {
    
    
            int temp=find(v,min(c[i],sum-res));
            if(!temp)
                d[v]=-1;
            c[i]-=temp;
            c[i^1]+=temp;
            res+=temp;
        }
    }
    return res;
}
void dinic()
{
    
    
    while(bfs())
        while(find(s,1e9));
}
void dfs(int u,bool st[],int vis)
{
    
    
    st[u]=true;
    for(int i=h[u];~i;i=ne[i])
    {
    
    
        int j=i^vis,v=e[i];
        if(!st[v]&&c[j])
            dfs(v,st,vis);
    }
}
int main()
{
    
    
    int n,m;
    scanf("%d%d",&n,&m);
    memset(h,-1,sizeof(h));
    t=n-1;
    for(int i=1;i<=m;i++)
    {
    
    
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);
    }
    dinic();
    dfs(s,vis_s,0);
    dfs(t,vis_t,1);
    int ans=0;
    for(int i=0;i<=idx;i+=2)
        if(vis_s[e[i^1]]&&vis_t[e[i]])
            ans++;
    printf("%d",ans);
    return 0;
}

AcWing2277——最大流判定

求的是最长路径最小,可以二分这个长度。如果大于这个长度的边就可以走一次,反之就不能走。每次跑一遍最大流,如果最大流大于等于 k k k,那么就能走 k k k次,反之不能。

#include<bits/stdc++.h>
using namespace std;
const int NN=204,MM=80004;
int h[NN],ne[MM],e[MM],w[MM],c[MM],idx=-1,head[NN],d[NN],s=1,t,k;
bool vis_s[NN],vis_t[NN];
void add(int u,int v,int x)
{
    
    
    ne[++idx]=h[u];
    e[idx]=v;
    w[idx]=x;
    h[u]=idx;
    ne[++idx]=h[v];
    e[idx]=u;
    w[idx]=x;
    h[v]=idx;
}
bool bfs()
{
    
    
    queue<int>q;
    q.push(s);
    memset(d,-1,sizeof(d));
    head[s]=h[s];
    d[s]=0;
    while(q.size())
    {
    
    
        int f=q.front();
        q.pop();
        for(int i=h[f];~i;i=ne[i])
        {
    
    
            int v=e[i];
            if(d[v]==-1&&c[i])
            {
    
    
                head[v]=h[v];
                d[v]=d[f]+1;
                if(v==t)
                    return true;
                q.push(v);
            }
        }
    }
    return false;
}
int find(int u,int sum)
{
    
    
    if(u==t)
        return sum;
    int res=0;
    for(int i=head[u];~i&&res<sum;i=ne[i])
    {
    
    
        head[u]=i;
        int v=e[i];
        if(d[v]==d[u]+1&&c[i])
        {
    
    
            int temp=find(v,min(c[i],sum-res));
            if(!temp)
                d[v]=-1;
            c[i]-=temp;
            c[i^1]+=temp;
            res+=temp;
        }
    }
    return res;
}
int dinic()
{
    
    
    int res=0,sum;
    while(bfs())
        while(sum=find(s,1e9))
            res+=sum;
    return res;
}
void dfs(int u,bool st[],int vis)
{
    
    
    st[u]=true;
    for(int i=h[u];~i;i=ne[i])
    {
    
    
        int j=i^vis,v=e[i];
        if(!st[v]&&c[j])
            dfs(v,st,vis);
    }
}
bool check(int x)
{
    
    
    for(int i=0;i<=idx;i++)
        if(w[i]>x)
            c[i]=0;
        else
            c[i]=1;
    return dinic()>=k;
}
int main()
{
    
    
    int n,m;
    scanf("%d%d%d",&n,&m,&k);
    memset(h,-1,sizeof(h));
    t=n;
    for(int i=1;i<=m;i++)
    {
    
    
        int u,v,x;
        scanf("%d%d%d",&u,&v,&x);
        add(u,v,x);
    }
    int l=1,r=1e9;
    while(l<r)
    {
    
    
        int mid=l+(r-l)/2;
        if(check(mid))
            r=mid;
        else
            l=mid+1;
    }
    printf("%d",r);
    return 0;
}

AcWing 2187——最大流判定

第零天向地球供给人数,每次可以在某一个地方等待一天,就是向表示后一天的该点连一条容量为正无穷的边。或者坐一次太空飞船,从表示当天的点 u u u的点,向表示下一天的点 v v v的点,连一条容量为飞船的容纳限制的边。不断累加最大流,直到哪天流量有 k k k为止。边是每次天数增加不断连的。为了一天只能走一步,日期不同的点的编号不一样,都是从第 i i i天的点连向第 i + 1 i+1 i+1天的点。

#include<bits/stdc++.h>
using namespace std;
const int NN=10004,MM=(1e5+NN)*2,KK=34;
int e[MM],ne[MM],c[MM],h[NN],fa[KK],idx=-1,head[NN],d[NN],s=NN-2,t=NN-1,n,m,k;
struct node
{
    
    
    int l,r,id[KK];
}work[KK];
int find(int x)
{
    
    
    return fa[x]==x?x:find(fa[x]);
}
int calc(int x,int day)
{
    
    
    return day*(n+2)+x;
}
void add(int u,int v,int w)
{
    
    
    e[++idx]=v;
    ne[idx]=h[u];
    c[idx]=w;
    h[u]=idx;
    e[++idx]=u;
    ne[idx]=h[v];
    h[v]=idx;
}
bool bfs()
{
    
    
    queue<int>q;
    memset(d,-1,sizeof(d));
    q.push(s);
    d[s]=0;
    head[s]=h[s];
    while(q.size())
    {
    
    
        int f=q.front();
        q.pop();
        for(int i=h[f];~i;i=ne[i])
        {
    
    
            int v=e[i];
            if(d[v]==-1&&c[i])
            {
    
    
                d[v]=d[f]+1;
                head[v]=h[v];
                if(v==t)
                    return true;
                q.push(v);
            }
        }
    }
    return false;
}
int find(int u,int sum)
{
    
    
    if(u==t)
        return sum;
    int res=0;
    for(int i=head[u];~i&&res<sum;i=ne[i])
    {
    
    
        int v=e[i];
        head[u]=i;
        if(d[v]==d[u]+1&&c[i])
        {
    
    
            int t=find(v,min(c[i],sum-res));
            if(!t)
                d[v]=-1;
            c[i]-=t;
            c[i^1]+=t;
            res+=t;
        }
    }
    return res;
}
int dinic()
{
    
    
    int res=0,sum;
    while(bfs())
        while(sum=find(s,1e9))
            res+=sum;
    return res;
}
int main()
{
    
    
    scanf("%d%d%d",&n,&m,&k);
    memset(h,-1,sizeof(h));
    for(int i=0;i<30;i++)
        fa[i]=i;
    for(int i=0;i<m;i++)
    {
    
    
        int a,b;
        scanf("%d%d",&a,&b);
        work[i]={
    
    a,b};
        for(int j=0;j<b;j++)
        {
    
    
            int id;
            scanf("%d",&id);
            if(id==-1)
                id=n+1;
            work[i].id[j]=id;
            if(j)
                fa[find(work[i].id[j-1])]=find(id);
        }
    }
    if(find(0)!=find(n+1))
    {
    
    
        printf("0");
        return 0;
    }
    add(s,calc(0,0),k);
    add(calc(n+1,0),t,1e9);
    int day=1,res=0;
    while(1)
    {
    
    
        add(calc(n+1,day),t,1e9);
        for(int i=0;i<=n+1;i++)
            add(calc(i,day-1),calc(i,day),1e9);
        for(int i=0;i<m;i++)
        {
    
    
            int r=work[i].r;
            add(calc(work[i].id[(day-1)%r],day-1),calc(work[i].id[day%r],day),work[i].l);
        }
        res+=dinic();
        if(res>=k)
            break;
        day++;
    }
    printf("%d",day);
    return 0;
}

习题

AcWing 2237

解析和代码在下一篇博客——最小割给出

猜你喜欢

转载自blog.csdn.net/weixin_44043668/article/details/108682905
今日推荐