网络流总结

1.最大流
有一个有向图,u到v的有向边表示水可以从u流向v
边上有容量,表示水最多流过去的量
有一个源点一个汇点,从源点这倒水,水通过流网络流向汇点,问这个流网络最多可以承受多少倒进去的水
这个问题就是最大流
在解决实际问题时主要还是建模比较考思维
有几个现成的例子比如二分图跑最大流,或者由最大流可以解决最小割而引出的最大密度子图这些
还有一个分支就是平面图最大流(狼抓兔子)考到了平面图的对偶图
这是暑假讲的了
贴一个模板

//总点数n+m+2,编号从0开始,源点0,汇点n+m+1
struct node{
    int v,w,next,op;
}edge[360020];
int head[360020],d[60000],vd[60000],n,m,flow,cnt;
void addedge(int u,int v,int w)
{
    edge[++cnt].v=v;
    edge[cnt].w=w;
    edge[cnt].op=cnt+1;
    edge[cnt].next=head[u];
    head[u]=cnt;
    edge[++cnt].v=u;
    edge[cnt].w=0;
    edge[cnt].op=cnt-1;
    edge[cnt].next=head[v];
    head[v]=cnt;
}
int aug(int u,int augco)
{
    if(u==n+m+1)return augco;
    int augc=augco,mind=n+m+1;
    for(int i=head[u];i;i=edge[i].next)
    {
        int v=edge[i].v;
        if(d[u]==d[v]+1&&edge[i].w>0)
        {
            int delta=min(augc,edge[i].w);
            delta=aug(v,delta);
            edge[i].w-=delta;
            edge[edge[i].op].w+=delta;
            augc-=delta;
            if(d[0]>=n+m+2)return augco-augc;
            if(augc==0)break;
        }
        if(edge[i].w>0)mind=min(mind,d[v]);
    }
    if(augco==augc)
    {
        vd[d[u]]--;
        if(vd[d[u]]==0)d[0]=n+m+2;
        else
        {
            d[u]=mind+1;
            vd[d[u]]++;
        }
    }
    return augco-augc;
}
void sap()
{
    vd[0]=n+m+2;
    while(d[0]<n+m+2)flow+=aug(0,999999999);
}

2.费用流
还是上面那个问题,不过现在流网络不是你家开的,每用一条边流水,都要交钱,每单位的水对于不同的边交不同的钱,还是想流过去的水最多,而且要交的钱最少
这是最小费用最大流
建模的时候有个技巧就是拆点,把一个点拆成两个中间连一条边表示选这个东西的费用
写法好像挺多的样子。。学习了一下SPFA和zkw的费用流
实测zkw很多时候要快一些
贴两个写法的模板
POJ 2195

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#define pii pair<int,int>
using namespace std;
vector<pii>H,M;
struct node{
    int v,next,cost,cap;
}edge[100000];
int n,m,cnt,src,des,ans,head[300],dis[300];
bool vis[300],inq[300];
char c;
int abs(int x)
{
    return x<0?-x:x;
}
int getd(pii x,pii y)
{
    return abs(x.first-y.first)+abs(x.second-y.second);
}
void addedge(int u,int v,int cost,int cap)
{
    edge[cnt].v=v;
    edge[cnt].cost=cost;
    edge[cnt].cap=cap;
    edge[cnt].next=head[u];
    head[u]=cnt++;
}
bool spfa()
{
    queue<int>q;
    q.push(des);
    memset(dis,0x3f,sizeof dis);
    inq[des]=1;
    dis[des]=0;
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        inq[u]=0;
        for(int i=head[u];~i;i=edge[i].next)
        {
            int v=edge[i].v;
            if(edge[i^1].cap>0&&dis[v]>dis[u]+edge[i^1].cost)
            {
                dis[v]=dis[u]+edge[i^1].cost;
                if(!inq[v])q.push(v),inq[v]=1;
            }
        }
    }
    if(dis[src]==0x3f3f3f3f)return 0;
    return 1;
}
int aug(int u,int augco)
{
    if(u==des)
    {
        ans+=dis[src]*augco;
        return augco;
    }
    int augc=augco,delta;
    for(int i=head[u];~i;i=edge[i].next)
        if(!vis[edge[i].v]&&edge[i].cap>0&&dis[edge[i].v]==dis[u]-edge[i].cost)
        {
            vis[edge[i].v]=1;
            delta=aug(edge[i].v,min(augc,edge[i].cap));
            augc-=delta;
            edge[i].cap-=delta;
            edge[i^1].cap+=delta;
        }
    return augco-augc;
}
int main()
{
    while(scanf("%d%d",&n,&m)&&n&&m)
    {
        H.clear();
        M.clear();
        memset(inq,0,sizeof inq);
        memset(head,-1,sizeof head);
        cnt=ans=0;
        for(int i=1;i<=n;++i)
        {
            c=getchar();
            for(int j=1;j<=m;++j)
            {
                c=getchar();
                if(c=='H')H.push_back(pii(i,j));
                else if(c=='m')M.push_back(pii(i,j));
            }
        }
        int hsz=H.size(),msz=M.size();
        for(int i=0;i<msz;++i)
        {
            addedge(1,i+2,0,1);
            addedge(i+2,1,0,0);
            for(int j=msz;j<hsz+msz;++j)
            {
                int d=getd(M[i],H[j-msz]);
                addedge(i+2,j+2,d,1);
                addedge(j+2,i+2,-d,0);
            }
        }
        for(int i=msz;i<hsz+msz;++i)
        {
            addedge(i+2,hsz+msz+2,0,1);
            addedge(hsz+msz+2,i+2,0,0);
        }
        src=1,des=hsz+msz+2;
        while(spfa())
        {
            memset(vis,0,sizeof vis);
            vis[src]=1;
            aug(src,999999999);
        }
        printf("%d\n",ans);
    }
}
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#define pii pair<int,int>
using namespace std;
vector<pii>H,M;
struct node{
    int v,next,cost,cap;
}edge[100000];
int n,m,cnt,src,des,ans,head[300],dis[300];
bool vis[300];
char c;
int abs(int x)
{
    return x<0?-x:x;
}
int getd(pii x,pii y)
{
    return abs(x.first-y.first)+abs(x.second-y.second);
}
void addedge(int u,int v,int cost,int cap)
{
    edge[cnt].v=v;
    edge[cnt].cost=cost;
    edge[cnt].cap=cap;
    edge[cnt].next=head[u];
    head[u]=cnt++;
}
bool label()
{
    int mi=999999999;
    for(int i=src;i<=des;++i)
    {
        if(vis[i])
        {
            for(int j=head[i];~j;j=edge[j].next)
                if(!vis[edge[j].v]&&edge[j].cap>0)
                    mi=min(mi,edge[j].cost+dis[edge[j].v]-dis[i]);
        }
    }
    if(mi==999999999)return 0;
    for(int i=src;i<=des;++i)
        if(vis[i])dis[i]+=mi;
    return 1;
}
int aug(int u,int augco)
{
    if(u==des)
    {
        ans+=dis[src]*augco;
        return augco;
    }
    int augc=augco,delta;
    vis[u]=1;
    for(int i=head[u];~i&&augc>0;i=edge[i].next)
        if(edge[i].cap>0&&!vis[edge[i].v]&&edge[i].cost+dis[edge[i].v]==dis[u])
        {
            delta=aug(edge[i].v,min(edge[i].cap,augc));
            augc-=delta;
            edge[i].cap-=delta;
            edge[i^1].cap+=delta;
        }
    return augco-augc;
}
int main()
{
    while(scanf("%d%d",&n,&m)&&n&&m)
    {
        H.clear();
        M.clear();
        memset(dis,0,sizeof dis);
        memset(vis,0,sizeof vis);
        memset(head,-1,sizeof head);
        cnt=ans=0;
        for(int i=1;i<=n;++i)
        {
            c=getchar();
            for(int j=1;j<=m;++j)
            {
                c=getchar();
                if(c=='H')H.push_back(pii(i,j));
                else if(c=='m')M.push_back(pii(i,j));
            }
        }
        int hsz=H.size(),msz=M.size();
        for(int i=0;i<msz;++i)
        {
            addedge(1,i+2,0,1);
            addedge(i+2,1,0,0);
            for(int j=msz;j<hsz+msz;++j)
            {
                int d=getd(M[i],H[j-msz]);
                addedge(i+2,j+2,d,1);
                addedge(j+2,i+2,-d,0);
            }
        }
        for(int i=msz;i<hsz+msz;++i)
        {
            addedge(i+2,hsz+msz+2,0,1);
            addedge(hsz+msz+2,i+2,0,0);
        }
        src=1,des=hsz+msz+2;
        do
        {
            do
            {
                memset(vis,0,sizeof vis);
            }while(aug(src,999999999));
        }while(label());
        printf("%d\n",ans);
    }
}

然后上面说的是最小费用流
现在假设这个流网络是你家开的,别人来你这灌水,你收别人钱
你想找一个方案,让他灌的水最多并且给你的钱最多
这个是最大费用流
写法一样,费用存进去的时候记得取反
得到答案后记得还原
3.上下界网络流
有三个子问题:
a.无源汇点的可行流
网络流当中没有源点汇点,那么这一定是一个循环流,即水流绕圈流动
问存不存在这样的流
这个麻烦在有上下界,普通网络流只有上界而无下界,所以这驱使我们思考怎么消除下界影响
假设一个点下界流进去a,下界流出去b
如果a大于b,那么说明下界流进去很多,而只满足下界的话流出来不够,需要自由流(就是在上下界之间的流量)来补
如果a小于b,那么说明下界流进去很少,而只满足下界的话流出来就已经超了,需要自由流(就是在上下界之间的流量)来补
所以我们现在考虑的问题就转化成了自由流怎么补满每个点
这就变成了一个普通的最大流问题了。
总结一下,先新建源点汇点,对每个点,统计流入的下界和流出的下界之差,如果流入的小于流出来的,就从源点往这个点连边,容量是差值的相反数
如果流入的大于流出的,就从这个点连一条容量为差值的边到汇点去
对于原图中的点,如果有边相连那么容量是上界-下界
最后跑最大流,如果源点出去的每个边满流那么有可行流
贴一个模板SGU176

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
struct node{
    int v,next,cap;
}edge[100010];
int n,m,cnt,flow,src,des,head[210],upl[50010],dwl[50010],io[210],d[210],vd[210];
void addedge(int u,int v,int cap)
{
    edge[cnt].v=v;
    edge[cnt].cap=cap;
    edge[cnt].next=head[u];
    head[u]=cnt++;
}
int aug(int u,int augco)
{
    if(u==n+1)return augco;
    int augc=augco,mind=n+1;
    for(int i=head[u];~i;i=edge[i].next)
    {
        int v=edge[i].v;
        if(d[u]==d[v]+1&&edge[i].cap>0)
        {
            int delta=aug(v,min(augc,edge[i].cap));
            edge[i].cap-=delta;
            augc-=delta;
            edge[i^1].cap+=delta;
            if(d[0]>=n+2)return augco-augc;
            if(augc==0)break;
        }
        if(edge[i].cap>0)mind=min(mind,d[v]);
    }
    if(augco==augc)
    {
        vd[d[u]]--;
        if(vd[d[u]]==0)d[0]=n+2;
        else
        {
            d[u]=mind+1;
            vd[d[u]]++;
        }
    }
    return augco-augc;
}
void sap()
{
    vd[0]=n+2;
    while(d[0]<n+2)flow+=aug(0,2147483640);
    for(int i=head[src];~i;i=edge[i].next)
        if(edge[i].cap)
        {
            puts("NO");
            return;
        }
    puts("YES");
    for(int i=0;i<m;++i)
        printf("%d\n",dwl[i]+edge[i*2+1].cap);
}
int main()
{
    memset(head,-1,sizeof head);
    scanf("%d%d",&n,&m);
    src=0,des=n+1;
    for(int i=0,u,v;i<m;++i)
    {
        scanf("%d%d%d%d",&u,&v,&dwl[i],&upl[i]);
        addedge(u,v,upl[i]-dwl[i]);
        addedge(v,u,0);
        io[u]-=dwl[i];
        io[v]+=dwl[i];
    }
    for(int i=1;i<=n;++i)
    {
        if(io[i]>0)addedge(src,i,io[i]),addedge(i,src,0);
        else if(io[i]<0)addedge(i,des,-io[i]),addedge(des,i,0);
    }
    sap();
}

b.有源汇点的最大流
对于这个图,首先要考虑的是有没有可行流。于是想办法把有源汇点的图转化成没有源汇点的图,方法很简单就是从汇点连一条容量为无穷大的边到源点,说白了就是你在源点灌水,汇点的人收到水之后把收到的水搬过来给你,你又往源点里面倒
这样流网络就只有循环流了
于是对这个没有源汇点的图跑一个可行流,建图方法同上
如果可行,那么考虑现在这个图的残留网络,显然边的下界都被满足了,然而自由流只是找了一条可行流出来,不一定最优,于是还可以对自由流跑最大流以求出原图的最大流,方法是删掉刚才新加的源点汇点和汇点到源点的边,注意到现在这个图就是只表示自由流了,对这个图求个最大流,答案就是刚才的可行流+现在的最大流
当然二分也可以,从汇点往源点连一条下界为a,上界无穷大的边,说白了你给在汇点的人说了,收到的水必须多于a才能搬回来,然后对这个图做可行流,看看可行不,通过二分调节a的取值最后得到最大值就是答案
c.有源汇点的最小流
既然有下界那么自然可以求最小流
方法是对原图做可行流,不过这次不加汇点到源点那条边,跑个最大流,然后再加上汇点到源点那条边再跑一次最大流。答案就是第二次跑的最大流。
感性理解是最终解只能是加上边后,求的无源汇可行流,即T->S这边上的流量. 不改成无源汇的直接求的解是未必正确的
然后,因为第一遍做的时候并无这条边,所以S->T的流量在第一遍做的时候都已经尽力往其他边流了. 于是加上T->S这条边后,都是些剩余的流不到其他边的流量. 从而达到尽可能减少T->S这边上的流量的效果,即减小了最终答案.本质是延迟对T->S这条边的增流.
当然二分也可以,从汇点往源点连一条下界为0,上界为a的边.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值