网络流总结

来源:互联网 发布:编辑图片大小的软件 编辑:程序博客网 时间:2024/06/06 03:30

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

//总点数n+m+2,编号从0开始,源点0,汇点n+m+1struct 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的边.

0 0
原创粉丝点击