转自:http://comzyh.tk/blog/archives/568/
转自:http://comzyh.tk/blog/archives/568/
“网络流博大精深”—sideman语
一个基本的网络流问题
感谢WHD的大力支持
最早知道网络流的内容便是最大流问题,最大流问题很好理解:
解释一定要通俗!
如右图所示,有一个管道系统,节点{1,2,3,4},有向管道{A,B,C,D,E},即有向图一张. [1]是源点,有无限的水量,[4]是汇点,管道容量如图所示.试问[4]点最大可接收的水的流量?
这便是简单的最大流问题,显然[4]点的最大流量为50
死理性派请注意:流量是单位时间内的,总可以了吧!
然而对于复杂图的最大流方法是什么呢,有EK,Dinic,SAP,etc.下面介绍Dinic算法(看代码的直接点这)
Dinic 算法
Dinic算法的基本思路:
1. 根据残量网络计算层次图。
2. 在层次图中使用DFS进行增广直到不存在增广路
3. 重复以上步骤直到无法增广
引自NOCOW,相当简单是吧...
小贴士:
一般情况下在Dinic算法中,我们只记录某一边的剩余流量.
· 残量网络:包含反向弧的有向图,Dinic要循环的,每次修改过的图都是残量网络,
· 层次图:分层图,以[从原点到某点的最短距离]分层的图,距离相等的为一层,(比如上图的分层为{1},{2,4},{3})
· DFS:这个就不用说了吧...
· 增广 :在现有流量基础上发现新的路径,扩大发现的最大流量(注意:增加量不一定是这条路径的流量,而是新的流量与上次流量之差)
· 增广路:在现有流量基础上发现的新路径.(快来找茬,和上一条有何不同?)
· 剩余流量:当一条边被增广之后(即它是增广路的一部分,或者说增广路通过这条边),这条边还能通过的流量.
· 反向弧:我们在Dinic算法中,对于一条有向边,我们需要建立另一条反向边(弧),当正向(输入数据)边剩余流量减少I时,反向弧剩余流量增加I
Comzyh的较详细解释(流程) :
Dinic动画演示
1. 用BFS建立分层图 注意:分层图是以当前图为基础建立的,所以要重复建立分层图
2. 用DFS的方法寻找一条由源点到汇点的路径,获得这条路径的流量I 根据这条路径修改整个图,将所经之处正向边流量减少I,反向边流量增加I,注意I是非负数
3. 重复步骤2,直到DFS找不到新的路径时,重复步骤1
注意(可以无视):
· Dinic(其实其他的好多)算法中寻找到增广路后要将反向边增加I
· Dinic中DFS时只在分层图中DFS,意思是说DFS的下一个节点的Dis(距源点的距离)要比自己的Dis大1,例如在图1中第一个次DFS中,1->2->4 这条路径是不合法的,因为Dis[2]=1;Dis[4]=1;
· 步骤2中"获得这条路径的流量I "实现:DFS函数有参量low,代表从源点到现在最窄的(剩余流量最小)的边的剩余流量,当DFS到汇点是,Low便是我们要的流量I
对于反向弧(反向边)的理解:
这一段不理解也不是不可以,对于会写算法没什么帮助,如果你着急,直接无视即可.
先举一个例子(如右图):
必须使用反向弧的流网络
在这幅图中我们首先要增广1->2->4->6,这时可以获得一个容量为 2的流,但是如果不建立4->2反向弧的话,则无法进一步增广,最终答案为2,显然是不对的,然而如果建立了反向弧4->2,则第二次能进行 1->3->4->2->5->6的增广,最大流为3.
Comzyh对反向弧的理解可以说是"偷梁换柱",请仔细阅读: 在上面的例子中,我们可以看出,最终结果是1->2->5->6和1->2->4->6和 1->3->4->6.当增广完1->2->4->6(代号A)后,在增广 1->3->4->2->5->6(代号B),相当于将经过节点2的A流从中截流1(总共是2)走2->5>6,而不走2->4>6了,同时B流也从节点4截流出1(总共是1)走4->6而不是4->2->5->6,相当于AB流做加法.
简单的说反向弧为今后提供反悔的机会,让前面不走这条路而走别的路.
最大流算法一直有一个入门经典题:POJ 1273 或者是UCACO 4_2_1 来自NOCOW(中文) 这两个是同一个题
给出这道题的代码
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<set>
#include<map>
#include<queue>
#include<vector>
#include<string>
#define Min(a,b) a<b?a:b
#define Max(a,b) a>b?a:b
#define CL(a,num) memset(a,num,sizeof(a));
#define eps 1e-12
#define inf 0x7fffffff
//freopen("data.txt","r",stdin);
const double pi = acos(-1.0);
typedef __int64 ll;
const int maxn = 300 ;
using namespace std;
int n , m;
int flow[maxn][maxn],dis[maxn] ;//dis[i],表示 到 原点 s 的 层数
int bfs()// 重新 建 图 (按 层数 建图)
{
CL(dis,-1);
dis[1] = 0 ;
queue<int>que;
que.push(1);
while(!que.empty())
{
int k = que.front();que.pop() ;
for( int i = 1;i<= n;i++)
{
if(flow[k][i] > 0 && dis[i] < 0 )// 如果 可以 可以到达 但 还没有 访问
{
dis[i] = dis[k]+ 1 ;
que.push(i) ;
}
}
}
if(dis[n] > 0) return 1;
else return 0 ;
}
int dfs(int x,int mx)// 查找 路径上的 最小 的 流量
{
int i , a ;
if(x == n) return mx ;
for(i = 1;i<= n;i++)
{
if(flow[x][i] > 0 && dis[i] == dis[x] + 1 && (a =dfs(i,min(mx,flow[x][i]))))
{
flow[x][i] -= a;
flow[i][x] += a;
return a ;
}
}
return 0 ;
}
int main()
{
//freopen("data.txt","r",stdin);
int i ,s,e,c;
while(scanf("%d%d",&m,&n)!=EOF)
{
CL(flow,0);
for(i = 0 ; i < m;i++)
{
scanf("%d%d%d",&s,&e,&c);
flow[s][e] += c;
}
int ans = 0;
int res;
while(bfs())
{
while(res = dfs(1,inf)) ans+= res ;
}
printf("%d\n",ans);
}
}
更高效的 dinic
hdu 4292 Food
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<set>
#include<map>
#include<queue>
#include<vector>
#include<string>
#define INF 0x3fffffff
#define F(x) (x)
#define N(x) (205+(x))
#define CPN(x) (410+(x))
#define D(x) (615+(x))
#define maxn 250
#define CL(a,b) memset(a,b,sizeof(a))
#define Pnum 210
using namespace std;
int next[maxn*20],dis[maxn*10];
int s,e;
int n, fnum ,dnum ,f[maxn],d[maxn],cnt;
struct node
{
int to;
int cap ;
int next ;
}p[200000] ;
int que[maxn*maxn] ,idx;
void add(int x,int y,int cap)// 建边 注意 反向边的流量为 0
{
p[cnt].to = y;
p[cnt].cap = cap ;
p[cnt].next = next[x];
next[x] = cnt++ ;
p[cnt].to = x;
p[cnt].cap = 0;
p[cnt].next = next[y];
next[y] = cnt++ ;
}
int bfs()// 重新 建 图 (按 层数 建图)
{
memset(dis,0xff,sizeof(dis)) ;
dis[s] = 0 ;
queue<int>que;
que.push(s);
while(!que.empty())
{
int k = que.front();que.pop() ;
for( int i = next[k];i!=-1;i = p[i].next)
{
int v = p[i].to;
int cap = p[i].cap ;
if(cap > 0 && dis[v] < 0 )// 如果 可以 可以到达 但 还没有 访问
{
dis[v] = dis[k]+ 1 ;
que.push(v) ;
}
}
}
if(dis[e] > 0) return 1;
else return 0 ;
}
int dfs(int x,int mx)// 查找 路径上的 最小 的 流量
{
int i , a ,tf = 0;
if(x == e) return mx ;
for(i = next[x];i!= - 1;i = p[i].next)
{
int v = p[i].to ;
int cap = p[i].cap ;
if(cap > 0 && dis[v] == dis[x] + 1 && (a =dfs(v,min(cap,mx))))
{
p[i].cap -= a;
p[i^1].cap += a;
return a;
}
}
if(!tf) dis[x] = -1;// 没有 找到 最小流量 ,说明 从这个点到不了 终点 ,所以 标记一下
return tf ;
}
int main()
{
int i , j ;
char c[250] ;
//freopen("data.txt","r",stdin) ;
while(scanf("%d%d%d",&n,&fnum,&dnum)!=EOF)
{
CL(next,-1) ;
cnt = 0;
s = 0;
e = 2000;
for(i = 1 ; i <= fnum;i++)
{
scanf("%d",&f[i]);
}
for(i = 1 ; i<= dnum;i++)
{
scanf("%d",&d[i]) ;
}
for(i = 1; i <= n;i++)// 人 和 吃的
{
scanf("%s",c);
for(j = 0 ; j< fnum ;j++)
{
if(c[j] == 'Y')
{
add(j + 1,i + Pnum,1) ;
}
}
}
for(i = 1; i<= n;i++)// 人 和 喝的
{
scanf("%s",c);
for(j = 0 ; j< dnum ;j++)
{
if(c[j] == 'Y')
{
add(i + Pnum*2,j + Pnum*3 + 1,1) ;
}
}
}
for(i = 1; i <= fnum;i++)//增加源点
{
add(0,i,f[i]) ;
}
for(i = Pnum*3 + 1,j = 1; j <= dnum;i++,j++)//增加 汇点
{
add(i,e,d[j]) ;
}
for(i = 1; i <= n;i++)// 将人 拆分
{
add(i + Pnum,i +Pnum*2,1);
}
int ans = 0;
int res;
while(bfs())
{
while(res = dfs(s,INF)) ans+= res ;
}
printf("%d\n",ans);
}
}