首先我想讲一下网络流的基础。
第一,要明白一些基础的概念,否则对后面的理解会有一定的影响。这里只列出常用的,列太多会影响阅读效率的。
底图:如果把一个有向图的每条边的方向都去掉,得到的无向图称为原有图的底图。
途径:图G中点边连续交替出现的序列称为G的一条途径。
迹:图G中边不重复出现的途径称为迹。
路:图G中顶点不重复出现的迹称为路。
网络的基本概念:
定义:一个网络N=(V,A)是指一个连通无环且满足下列条件的有向图:
1.有一个定点子集X,其每个顶点的入度都是0;
2.有一个与X不相交的顶点子集Y,其每个顶点的出度都为0;
3.每条弧都有一个非负的权值,称为弧的容量。
上述网络N记作N(V,X,Y,A,C),其中X为网络的源点集,Y称为网络的汇点集,V和A分别称为顶点集和弧集。C为网络的容量函数。
网络的可行流:
网络N=(V,X,Y,A,C)中的一个可行流是指定义在A上的一个整值函数f,使得:
1.对任意a属于A,0<=f(a)<=c(a)(容量约束);
2.对任意v属于 V-(XUY),f(v)=f’(v)(流量守恒);
其中f(v)表示点v的入弧的流量之和,f’(v)表示点v的出弧的流量之和。
最大流最小割定理:在任一网络中,最大流的流量等于最小割的容量。
此定理对于如何求最大流并没有什么用处,但是可以用他来证明最大流的正确性。
最大流的求解:
求网络中的最大流基本思想都是不断地增加流量,直到不能再增加为止。这就引入了可增路的概念。
路:设u,v是网络N中任意两点,P是N的底图中的一条连接uv的路,若规定P的走向为从u到v,则称这样规定了走向的路P为网络N中一条从u到v的路,简称u-v路。特别的,一条从源点x到汇点y的路称为x-y路。
正向弧就是路中的边在原图中是否是正向弧。不是就是反向弧。
可增路:假设f是网络的一个可行流,u是N中任意一点,P是网络N中的一条x-u路,如果对路P上的任一弧a,都有:
1.若弧a是P的正向弧,则c(a)-f(a)>0;
2.若弧a是P的反向弧,则f(a)>0;
则P称为N的一条可增x-u路。特别的,一条f可增x-y路称为 N的一条f可增路。
对于N中任意一条f可增路P和P上任意一条弧a,假设
△f(a)= c(a)-f(a) (正向弧)
f(a) (反向弧)
则可增路的可增加流量为△f(P)=min{△f(a)};称为可增量。
最大流Dinic算法:
Dinic算法利用分层的思想对网络进行一些处理,简化了一些操作。
1.增量网络(残余网络)
对于网络N和N上的可行流f,构造一个新的网络N(f)=(V,X,Y,A(f),C’)中A(f)及容量函数C’定义如下:
(为何这么诡异,把这句好敲出来博客页面就显示不出下面的两张图和部分文字。。无奈,只好截图了。。。噗噗噗。。)
2.若(u,v)属于A并且f(u,v)>0,则(v,u)属于A(f),并且c’(u,v)=f(u,v);
分层就是bfs一遍扫描网络,求出每个点到源点的最短距离。
对分层后的网络进行下一步的操作就可以得到辅助网络。
辅助网络:对于增量网络N(f)进行分层后,删除层数不低于y的顶点,再删除从高层指向低层的弧和同层之间的弧。
其实代码中并没有很清晰的看到辅助网络的身影,直接是bfs分层,dfs计算△f(a),直到bfs不能达到汇点。
好了,说一下本题的题意吧。
题意:就是在一个网格中有些是羊,有两个是灰太狼和红太狼,问怎样用最少的栏杆拦截使小羊安全。
分析:
每个点都有向相邻点的连线,容量为1,狼都连向汇点,羊都连向源点,容量为inf。求最大流。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
using namespace std;
#define read freopen("q.in","r",stdin)
#define maxn 40004
#define inf 0x7fffffff
int edgeNum,n,m;
int d[maxn],head[maxn];
struct Edge
{
int to,flow,next;
}edge[maxn*4];
int from,to;
void add(int u,int v,int flow)
{
edge[edgeNum].to=v;edge[edgeNum].flow=flow;edge[edgeNum].next=head[u];head[u]=edgeNum++;
edge[edgeNum].to=u;edge[edgeNum].flow=0;edge[edgeNum].next=head[v];head[v]=edgeNum++;
}
bool bfs()
{
memset(d,0,sizeof(d));
int i,k,j;
queue<int> q;
d[from]=1;
q.push(from);
while(!q.empty())
{
int u=q.front();q.pop();
for(i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].to;
if(!d[v] && edge[i].flow>0)
{
d[v]=d[u]+1;
q.push(v);
if(v==to)return true;
}
}
}
return false;
}
int dfs(int u,int flow)
{
if(u==to || flow==0)return flow;
int i,j,k;
int cap=flow;
for(i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].to;
if(d[v]==d[u]+1 && edge[i].flow>0)
{
int x=dfs(v,min(cap,edge[i].flow));
cap-=x;
edge[i].flow-=x;
edge[i^1].flow+=x;
if(cap==0)return flow;
}
}
return flow-cap;
}
int dinic()
{
int sum=0;
while(bfs())sum+=dfs(from,inf);
return sum;
}
int main()
{
// read;
int i,j,x,cas=1;
while(~scanf("%d%d",&n,&m))
{
from=n*m+1;to=n*m+2;
edgeNum=0;
memset(head,-1,sizeof(head));
for(i=1;i<=n;i++)
{
for(j=1;j<=m;j++)
{
scanf("%d",&x);
if(x==1)add(from,(i-1)*m+j,inf);
if(x==2)add((i-1)*m+j,to,inf);
if(i-1>=1 )add((i-1)*m+j,(i-2)*m+j,1);
if(j-1>=1 )add((i-1)*m+j,(i-1)*m+j-1,1);
if(i+1<=n )add((i-1)*m+j,i*m+j,1);
if(j+1<=m )add((i-1)*m+j,(i-1)*m+j+1,1);
}
}
int res=dinic();
cout<<"Case "<<cas++<<":\n"<<res<<endl;
}
}
ps:我觉得讲算法附上代码很重要。