有上下界的网络流&费用流消圈算法

无源汇可行流

就是一个网络,每条边有一个流量的下限和上限,要求给每条边安排一个流量,使得所有点进出的流量相同。
考虑强制让下限流满,这样必定会造成点的出入流量不平衡,这个问题可以通过建源点汇点来解决。
具体做法是把每条边容量设为上限-下限的值,然后对每个点计算出流入的边的下限之和-流出的边的下限之和的值。如果这个值是正的,就从源点向它连一条容量为这个值的边,表示强制在自由流量中,出的流量比入的流量多这么多;否则向汇点连相反数的容量的边,意义类似。最后跑最大流,判断一下是否每条从源点连出去的边都流满即可。
loj有模板题。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int mxn=65536;
int N,M,S,T,head[mxn],cur[mxn],gap[mxn],dep[mxn];
struct ed{int to,nxt,val;}edge[mxn];
void addedge(int u,int v,int w){
    edge[++M]=(ed){v,head[u],w},head[u]=M;
    edge[++M]=(ed){u,head[v],0},head[v]=M;
}
int dfs(int u,int mx){
    if (u==T) return mx;
    int num=0;
    for (int &i=cur[u],v;i;i=edge[i].nxt)
        if (dep[v=edge[i].to]==dep[u]-1&&edge[i].val){
            int f=dfs(v,min(mx-num,edge[i].val));
            edge[i].val-=f,edge[i^1].val+=f,num+=f;
            if (num==mx) return num;
        }
    if (!--gap[dep[u]++]) dep[S]=N+1;
    ++gap[dep[u]],cur[u]=head[u];
    return num;
}
int solve(){
    gap[1]=N;
    for (int i=1;i<=N;++i)
        dep[i]=1,cur[i]=head[i];
    int sum=0;
    for (;dep[S]<=N;sum+=dfs(S,0x3f3f3f3f));
    return sum;
}
int n,m,w[mxn],id[mxn],rb[mxn];
void sol(){
    solve();
    for (int i=head[S];i;i=edge[i].nxt)
        if (edge[i].val) return (void)puts("NO");
    puts("YES");
    for (int i=1;i<=m;++i)
        printf("%d\n",rb[i]-edge[id[i]].val);
}
int main()
{
    scanf("%d%d",&n,&m);
    N=n+2,M=1,S=n+1,T=N;
    for (int i=1;i<=m;++i){
        int x,y,l,r;
        scanf("%d%d%d%d",&x,&y,&l,&r);
        addedge(x,y,r-l);
        w[x]-=l,w[y]+=l;
        id[i]=M-1,rb[i]=r;
    }
    for (int i=1;i<=n;++i)
        if (w[i]>0) addedge(S,i,w[i]);
        else addedge(i,T,-w[i]);
    sol();
    return 0;
}

有源汇可行流

类似于上一个问题,图中有源点汇点,源点可以多流出一点,汇点可以多流入一点。
显然源点流出的量=汇点流入的量,从汇点到源点连一条容量为正无穷的边就可以转化为无源汇可行流。

有源汇最大流

相当于满足了下限,然后在自由流量上调整,使得流量最大。
所以先求出一个可行流,然后去掉加上去的那条正无穷的边,从原图的源点到汇点跑最大流即可。
loj有模板题。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int mxn=100010,inf=0x3f3f3f3f;
int N,M,S,T,head[mxn],cur[mxn],dep[mxn],gap[mxn];
struct ed{int to,nxt,val;}edge[mxn<<1];
void addedge(int u,int v,int w){
    edge[++M]=(ed){v,head[u],w},head[u]=M;
    edge[++M]=(ed){u,head[v],0},head[v]=M;
}
int dfs(int u,int mx){
    if (u==T) return mx;
    int num=0;
    for (int &i=cur[u],v;i;i=edge[i].nxt)
        if (dep[v=edge[i].to]==dep[u]-1&&edge[i].val){
            int f=dfs(v,min(mx-num,edge[i].val));
            edge[i].val-=f,edge[i^1].val+=f,num+=f;
            if (num==mx) return num;
        }
    if (!--gap[dep[u]++]) dep[S]=N+1;
    ++gap[dep[u]],cur[u]=head[u];
    return num;
}
int solve(){
    gap[1]=N;
    for (int i=1;i<=N;++i)
        dep[i]=1,cur[i]=head[i];
    int sum=0;
    for (;dep[S]<=N;sum+=dfs(S,inf));
    return sum;
}
int n,m,s,t,w[mxn];
void sol(){
    scanf("%d%d%d%d",&n,&m,&s,&t);
    N=n+2,M=1,S=n+1,T=N;
    for (int i=1;i<=m;++i){
        int x,y,l,r;
        scanf("%d%d%d%d",&x,&y,&l,&r);
        addedge(x,y,r-l);
        w[x]-=l,w[y]+=l;
    }
    for (int i=1;i<=n;++i)
        if (w[i]>0) addedge(S,i,w[i]);
        else addedge(i,T,-w[i]);
    addedge(t,s,inf);
    solve();
    for (int i=head[S];i;i=edge[i].nxt)
        if (edge[i].val) return (void)puts("please go home to sleep");
    int ans=edge[M].val;
    edge[M].val=edge[M^1].val=0;
    S=s,T=t;
    ans+=solve();
    printf("%d\n",ans);
}
int main()
{
    sol();
    return 0;
}

有源汇最小流

相当于要在自由网络上退回最多的流量,求出可行流,去掉那条边之后ans-=从原图汇点到源点的最大流即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int rd(){
    int x=0;
    char c=getchar();
    for (;c<48||c>57;c=getchar());
    for (;c>47&&c<58;x=x*10+c-48,c=getchar());
    return x;
}
const int mxn=500010,inf=2147483647;
int N,M,S,T,head[mxn],cur[mxn],dep[mxn],gap[mxn];
struct ed{int to,nxt,val;}edge[mxn<<1];
void addedge(int u,int v,int w){
    edge[++M]=(ed){v,head[u],w},head[u]=M;
    edge[++M]=(ed){u,head[v],0},head[v]=M;
}
int dfs(int u,int mx){
    if (u==T) return mx;
    int num=0;
    for (int &i=cur[u],v;i;i=edge[i].nxt)
        if (edge[i].val&&dep[v=edge[i].to]==dep[u]-1){
            int f=dfs(v,min(mx-num,edge[i].val));
            edge[i].val-=f,edge[i^1].val+=f,num+=f;
            if (num==mx) return num;
        }
    if (!--gap[dep[u]++]) dep[S]=N+1;
    ++gap[dep[u]],cur[u]=head[u];
    return num;
}
int solve(){
    gap[1]=N;
    for (int i=1;i<=N;++i)
        dep[i]=1,cur[i]=head[i];
    int sum=0;
    for (;dep[S]<=N;sum+=dfs(S,inf));
    return sum;
}
int n,m,s,t,w[mxn];
void sol(){
    n=rd(),m=rd(),s=rd(),t=rd();
    N=n+2,M=1,S=n+1,T=N;
    for (int i=1;i<=m;++i){
        int x=rd(),y=rd(),l=rd(),r=rd();
        addedge(x,y,r-l);
        w[x]-=l,w[y]+=l;
    }
    for (int i=1;i<=n;++i)
        if (w[i]>0) addedge(S,i,w[i]);
        else addedge(i,T,-w[i]);
    addedge(t,s,inf);
    solve();
    for (int i=head[S];i;i=edge[i].nxt)
        if (edge[i].val) return (void)puts("please go home to sleep");
    int ans=edge[M].val;
    edge[M].val=edge[M^1].val=0;
    S=t,T=s;
    ans-=solve();
    printf("%d\n",ans);
}
int main()
{
    sol();
    return 0;
}

有源汇最小费用可行流

和有源汇可行流不是一样的吗。。。
模板提bzoj3876

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int mxn=16384,inf=0x3f3f3f3f;
int N,M,S,T,dst,sum,flw,head[mxn],dis[mxn],vis[mxn],q[mxn];
struct ed{int to,nxt,val,fee;}edge[mxn];
void addedge(int u,int v,int w,int f){
    edge[++M]=(ed){v,head[u],w,f},head[u]=M;
    edge[++M]=(ed){u,head[v],0,-f},head[v]=M;
}
int dfs(int u,int mx){
    if (u==T) return sum+=dst*mx,mx;
    int num=0;
    vis[u]=1;
    for (int i=head[u],v;i;i=edge[i].nxt)
        if (!vis[v=edge[i].to]&&!edge[i].fee&&edge[i].val){
            int f=dfs(v,min(mx-num,edge[i].val));
            edge[i].val-=f,edge[i^1].val+=f,num+=f;
            if (num==mx) return vis[u]=0,num;
        }
    return num;
}
bool bfs(){
    memset(dis,0x3f,sizeof(dis));
    memset(vis,0,sizeof(vis));
    q[1]=T,dis[T]=0;
    for (int hd=0,tl=1;hd!=tl;){
        int u=q[hd=hd%N+1];
        vis[u]=0;
        for (int i=head[u],v;i;i=edge[i].nxt)
            if (dis[v=edge[i].to]>dis[u]-edge[i].fee&&edge[i^1].val){
                dis[v]=dis[u]-edge[i].fee;
                if (!vis[v]){
                    vis[v]=1;
                    if (hd!=tl&&dis[q[hd+1]]>dis[v]) q[hd]=v,hd=hd-1+(hd<2)*N;
                    else q[tl=tl%N+1]=v;
                }
            }
    }
    for (int u=1;u<=N;++u)
        for (int i=head[u];i;i=edge[i].nxt)
            edge[i].fee-=dis[u]-dis[edge[i].to];
    dst+=dis[S];
    return dis[S]<inf;
}
void solve(){
    for (;bfs();flw+=dfs(S,inf));
}
int n,m,s,t,ans,w[mxn];
int main()
{
    scanf("%d",&n);
    s=n+1,t=n+2;
    N=t+2,M=1,S=t+1,T=N;
    addedge(s,1,inf,0);
    for (int i=1;i<=n;++i) addedge(i,t,inf,0);
    for (int i=1;i<=n;++i){
        scanf("%d",&m);
        for (int j=1,x,y;j<=m;++j){
            scanf("%d%d",&x,&y);
            addedge(i,x,inf,y);
            --w[i],++w[x];
            ans+=y;
        }
    }
    for (int i=1;i<=t;++i)
        if (w[i]>0) addedge(S,i,w[i],0);
        else addedge(i,T,-w[i],0);
    addedge(t,s,inf,0);
    solve();
    ans+=sum;
    printf("%d\n",ans);
    return 0;
}

费用流消圈算法

GDSOI2019D1T4,一道上下界费用流模板题,但是。。。有负环。
基于spfa的费用流肯定是不适用的,这里介绍一种消圈求费用流的算法。
首先求出最大流,这时候原图不存在增广路,想要求出更优解,增广的过程肯定是一个负环。
所以每次找出一个负环,手动把流量修改一下就可以了。
注意判负环时找到的那一个点不一定在负环上,所以要记一个\(pre\),每次往前跳直到跳到已经跳过的点为止。
复杂度不知道,反正很慢。
这题自己构造的数据跑了4s,不过用spfa判环应该就不会这么慢了?

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int mxn=16384,inf=0x3f3f3f3f;
int N,M,S,T,dst,sum,flw,head[mxn],cur[mxn],dep[mxn],gap[mxn];
struct ed{int to,nxt,val,fee;}edge[mxn];
void addedge(int u,int v,int w,int f){
    edge[++M]=(ed){v,head[u],w,f},head[u]=M;
    edge[++M]=(ed){u,head[v],0,-f},head[v]=M;
}
int dfs(int u,int mx){
    if (u==T) return mx;
    int num=0;
    for (int &i=cur[u],v;i;i=edge[i].nxt)
        if (dep[v=edge[i].to]==dep[u]-1&&edge[i].val){
            int f=dfs(v,min(mx-num,edge[i].val));
            edge[i].val-=f,edge[i^1].val+=f,num+=f;
            if (num==mx) return num;
        }
    if (!--gap[dep[u]++]) dep[S]=N+1;
    ++gap[dep[u]],cur[u]=head[u];
    return num;
}
int MaxFlow_solve(){
    gap[1]=N;
    for (int i=1;i<=N;++i)
        dep[i]=1,cur[i]=head[i];
    int flw=0;
    for (;dep[S]<=N;flw+=dfs(S,inf));
    return flw;
}
int dis[mxn],pre[mxn];
int check(){
    memset(dis,0,sizeof(dis));
    for (int i=1;i<N;++i)
        for (int j=2;j<=M;++j)
            if (edge[j].val){
                int u=edge[j^1].to,v=edge[j].to,w=edge[j].fee;
                if (dis[v]>dis[u]+w) dis[v]=dis[u]+w,pre[v]=j;
            }
    for (int j=2;j<=M;++j)
        if (edge[j].val){
            int u=edge[j^1].to,v=edge[j].to,w=edge[j].fee;
            if (dis[v]>dis[u]+w) return v;
        }
    return 0;
}
void upd(int i){
    int fl=inf;
    for (int j=i;;){
        fl=min(fl,edge[j].val);
        j=pre[edge[j^1].to];
        if (j==i) break;
    }
    for (int j=i;;){
        edge[j].val-=fl,edge[j^1].val+=fl;
        j=pre[edge[j^1].to];
        if (j==i) break;
    }
}
int solve(){
    for (int u=check();u;u=check()){
        for (;dis[u]<=0;dis[u]=1,u=edge[pre[u]^1].to);
        upd(pre[u]);
    }
    int sum=0;
    for (int i=3;i<=M;i+=2)
        sum-=edge[i].val*edge[i].fee;
    return sum;
}
int n,m,k,s,t,sa,sb,a[64][64];
int main()
{
    freopen("in.txt","r",stdin);
    scanf("%d%d%d",&n,&m,&k);
    s=n+m+1,t=n+m+2;
    N=n+m+4,M=1,S=N-1,T=N;
    for (int i=1,l,r;i<=n;++i){
        scanf("%d%d",&l,&r);
        addedge(s,i,r-l,0);
        addedge(S,i,l,0);
        sa+=l;
    }
    for (int i=1,l,r;i<=m;++i){
        scanf("%d%d",&l,&r);
        addedge(n+i,t,r-l,0);
        addedge(n+i,T,l,0);
        sb+=l;
    }
    addedge(s,T,sa,0);
    addedge(S,t,sb,0);
    for (int i=1,x,y;i<=k;++i)
        scanf("%d%d",&x,&y),a[x][y]=1;
    for (int i=1;i<=n;++i)
        for (int j=1;j<=m;++j)
            if (!a[i][j]) addedge(i,n+j,1,1);
            else addedge(i,n+j,1,-1);
    addedge(t,s,inf,0);
    MaxFlow_solve();
    k+=solve();
    printf("%d\n",k);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/zzqtxdy/p/11967846.html