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的边.