最大流

基本知识:  流网络G = (V,E)是一个有向图,其中每条边有一非负容量c(u,v) >= 0。如果(u,v) 不属于E,则假定c(u,v) = 0。

       两个特殊点:源点:S  汇点: T              

         c:容量函数   f:实值函数(也就是最大流)

 

三个性质:   容量限制:对所有边,f(u,v) <= c(u,v);

      反对称性:对所有边,f(u,v) = - f(u,v);

      流守恒性:对除源点和汇点外的任意点,该点的流的为0。该点的流记为:|f|。

 

三种重要思想:  残留网络:Gf = (V,Ef) ,  其中边(u,v)的残留流量cf(u,v) = c(u,v) - f(u,v)  即可以再增加的流量值。

             Ef的建立:if 边(u,v) 属于 E

                    if f(u,v) < c(u,v)

                     then cf(u,v) = c(u,v) - f(u,v) > 0

                    if   f(u,v) > 0    so  f(v,u) < 0

                     then cf(v,u) = c(v,u) - f(v,u) > 0

                  else if 边(u,v)和(v,u)都不属于E   so c(u,v) = c(v,u) = 0,f(u,v) = f(v,u) = 0

                     then cf(u,v) = cf(v,u) = c(u,v) - f(u,v) = c(v,u) - f(v,u) = 0

 

                  SO   cf(u,v) = c(u,v) - f(u,v)

        增广路径:在Gf中一条从s到t的简单路径。最大流算法就是求增广路径,优化也是对这个进行优化。

        流网络的割G中割(S,T)将V划分成S和T=E-S两部分,使得s属于S,t属于T。

              f(S,T):穿过割(S,T)的净流,包括从S到T的流量,也包括从T到S的流量

              c(S,T):割(S,T)的容量,只包括从S到T的容量

              对所有割,其f都是相同的,但c是有大小的

              最小割:G中所有割中具有最小容量的割

 

最大流最小割定理:f是具有s和t的G中的一个流,下面三个定理等价:

         1:f是G的一个最大流

         2:Gf不包含增广路径

         3:对G中的某个割,有|f| = c(S,T)

 

 

Ford-Fulkerson方  为什么叫Ford-Fulkerson方法而不是算法,原因在于可以用多种方式实现这一方法,方式并不唯一。

           在每次迭代中,找出任意增广路径p,并把沿p每条边的流f加上其残留容留cf(p)。

 

Edmonds-Karp算法基于广度优先搜索(BFS)来计算增广路径P  时间复杂度O(V*E^2)

           算法流程如下:

            设队列Q--存储当前未检查的标号点,队首节点出队后,成为已检查的标点;

            path -- 存储当前已标号可改进路经;

 

            repeat

                   path置空;

                  源点s标号并进入path和Q;

                   while  Q非空  and  汇点t未标号 do

                          begin

                                 移出Q的队首顶点u;

                                 for 每一条从u出发的弧(u,v) do

                                        if  v未标号 and 弧(u,v)的流量可改进

                        then v进入队列Q和path;

                      end while

                    if 汇点已标号

                  then 从汇点出发沿着path修正可改进路的流量;

              until 汇点未标号;

   

  Edmonds-Karp算法有一个很重要的性质:当汇点未标号而导致算法结束的时候,那些已经标号的节点构成集合S,未标号的节点构成集合T,割(S,T)恰好是该流网络的最小割;且这样求出的最小割(S,T)中集合S的元素数目一定是最少的。

  BFS遍历增广路径代码:

HDU:1532 http://acm.hdu.edu.cn/showproblem.php?pid=1532

ContractedBlock.gif ExpandedBlockStart.gif 代码
 
   
#include < iostream >
#include
< queue >
using namespace std;

#define min(a,b) (a>b?b:a)

int n,m; // n条边,m个点
int Start,End;
queue
< int > Q;
const long maxn = 205 ;
const long inf = 1000000000 ;

int net[maxn][maxn]; // 残留网络
int path[maxn]; // 增广路径
int flow[maxn]; // 增广路径的通过容量

void Init()
{
int i;
int from,to,val;

memset(net,
0 , sizeof (net));

for (i = 0 ;i < n;i ++ )
{
scanf(
" %d%d%d " , & from, & to, & val);
net[from][to]
+= val;
}

Start
= 1 ;
End
= m;
// scanf("%d%d",&Start,&End);
}

int bfs()
{
// 寻找增广路径
while ( ! Q.empty())
{
Q.pop();
}
memset(path,
- 1 , sizeof (path));

path[Start]
= 0 ;
flow[Start]
= inf;

Q.push(Start);
while ( ! Q.empty())
{
int top = Q.front();
Q.pop();

if (top == End)
break ;

int i;
for (i = 1 ;i <= m;i ++ )
{
if (i != Start && - 1 == path[i] && net[top][i])
{
flow[i]
= min(flow[top],net[top][i]);
Q.push(i);
path[i]
= top;
}
}
}

if ( - 1 == path[End])
return - 1 ;

return flow[m]; // 为什么是m,不是End
}

void print( int ans)
{
printf(
" %d\n " ,ans);
}

void Edmonds_Karp()
{
int max_flow = 0 ;

int step;

while ((step = bfs()) != - 1 ) // 不停的找增广路径,至到找不到
{
max_flow
+= step;

/* 对残留网络进行修改 */
int now = End;
while (now != Start)
{
int pre = path[now];
net[pre][now]
-= step;
net[now][pre]
+= step;
now
= pre;
}
}

print(max_flow);
}

int main()
{
while (scanf( " %d%d " , & n, & m) != EOF)
{
Init();
Edmonds_Karp();
}
return 0 ;
}


Dinic算法:

 2011011822543534.jpg
 

自己对着网上写的一个代码,还很挫,用空再改改:

ContractedBlock.gif ExpandedBlockStart.gif 代码
 
   
#include < iostream >
#include
< queue >
#include
< stack >
using namespace std;

const long maxn = 205 ;
const long inf = 1000000000 ;

#define max(a,b) (a>b?a:b)
#define min(a,b) (a<b?a:b)

int m,n;
int start,end;
int map[maxn][maxn];
int level[maxn];
int vis[maxn];

void Init()
{
int i,a,b,c;
memset(map,
0 , sizeof (map));
for (i = 0 ;i < m;i ++ )
{
scanf(
" %d%d%d " , & a, & b, & c);
map[a][b]
+= c;
}
start
= 1 ;
end
= n;
}

bool bfs()
{
int i;
memset(level,
- 1 , sizeof (level));

queue
< int > Q;
Q.push(start);
level[start]
= 0 ;

while ( ! Q.empty())
{
int top = Q.front();
Q.pop();

for (i = 1 ;i <= n;i ++ )
{
if (level[i] == - 1 && map[top][i])
{
level[i]
= level[top] + 1 ;
Q.push(i);
}
}
}

if (level[end] == - 1 )
return false ;

return true ;
}

int dfs( int ss, int limit)
{
int i;
if (ss == end)
return limit;

vis[ss]
= 1 ;

int flow = 0 ;
for (i = 1 ;i <= n;i ++ )
{
if (level[i] - level[ss] == 1 && map[ss][i] && vis[i] == 0 )
{
int temp = dfs(i,min(limit,map[ss][i]));
limit
-= temp;

map[ss][i]
-= temp;
map[i][ss]
+= temp;

flow
+= temp;

if (limit == 0 )
break ;
}
}
vis[ss]
= 0 ;

return flow;
}

int Dinic()
{
int max_flow = 0 ;
while (bfs())
{
memset(vis,
0 , sizeof (vis));
int flow = dfs(start,inf);
if (flow == 0 )
break ;
max_flow
+= flow;
}
return max_flow;
}
int main()
{
while (scanf( " %d%d " , & m, & n) != EOF)
{
Init();
int num = Dinic();

printf(
" %d\n " ,num);
}
return 0 ;
}

用邻接表写的

ContractedBlock.gif ExpandedBlockStart.gif 代码
 
   
#include < iostream >
using namespace std;

const long inf = 1000000000 ;
const long maxn_point = 205 ;
const long maxn_edge = 20005 ;

inline fmin(
int a, int b){ return a < b ? a:b;}
inline fmax(
int a, int b){ return a > b ? a:b;}

int n,m; // m条边,n个点
int start,end;
int Index;
struct node
{
int to;
int val;
int next;
}ditch[maxn_edge];
int pre[maxn_point];
int level[maxn_point],queue[maxn_point]; // 层次图,模拟队列

inline
void single_insert( int from, int to, int cap)
{
ditch[Index].to
= to;
ditch[Index].val
= cap;
ditch[Index].next
= pre[from];
pre[from]
= Index ++ ;
}
void insert( int from, int to, int cap)
{
single_insert(from,to,cap);
single_insert(to,from,
0 );
}

void Init()
{
int a,b,c;
Index
= 0 ;
memset(pre,
- 1 , sizeof (pre));

while (m -- )
{
scanf(
" %d%d%d " , & a, & b, & c);
insert(a,b,c);
}
start
= 1 ;
end
= n;
}

bool bfs()
{
int i;
int ed = 0 ,bg = 0 ,x,y;
memset(level,
- 1 , sizeof (level));
level[start]
= 0 ;
queue[ed
++ ] = start;

while (bg < ed)
{
x
= queue[bg ++ ];
for (i = pre[x];i + 1 ;i = ditch[i].next)
{
y
= ditch[i].to;
if (ditch[i].val && level[y] == - 1 )
{
level[y]
= level[x] + 1 ;
if (y == end) // 层次图不必要全部建完
return true ;
queue[ed
++ ] = y;
}
}
}
return false ;
}

int dfs( int x, int limit = inf)
{
int i,y,ret;
if (x == end)
return limit;

int flow = 0 ;
for (i = pre[x]; (i + 1 ) && limit; i = ditch[i].next)
{
y
= ditch[i].to;
if (ditch[i].val && level[y] == level[x] + 1 )
{
ret
= dfs(y,fmin(limit,ditch[i].val));

ditch[i].val
-= ret;
ditch[i
^ 1 ].val += ret;
limit
-= ret;
flow
+= ret;
}
}
return flow;
}

int Dinic()
{
int max_flow = 0 ;
while (bfs())
{
max_flow
+= dfs(start);
}

return max_flow;
}

int main()
{
while (scanf( " %d%d " , & m, & n) != EOF)
{
Init();
int num = Dinic();
printf(
" %d\n " ,num);
}
return 0 ;
}


hdu:3526 http://acm.hdu.edu.cn/showproblem.php?pid=3526

设置超级源点和超级汇点,建立流向图。最大流做法

ContractedBlock.gif ExpandedBlockStart.gif 代码
 
   
#include < iostream >
#include
< queue >
using namespace std;

#define fmax(a,b) (a>b?a:b)
#define fmin(a,b) (a<b?a:b)

const long maxn_point = 505 ;
const long maxn_edge = 500050 ;
const long inf = 1e9;


int n,m,Index;
int start,end;
struct node
{
int to;
int val;
int next;
}com[maxn_edge];
int pre[maxn_point];
int level[maxn_point];

void _insert( int from, int to, int val)
{
com[Index].to
= to;
com[Index].val
= val;
com[Index].next
= pre[from];
pre[from]
= Index ++ ;
}
void insert( int from, int to, int val)
{
_insert(from,to,val);
_insert(to,from,
0 );
}

void Init()
{
int i,a,b,c;
Index
= 0 ;
start
= 0 ,end = n + 1 ;
memset(pre,
- 1 , sizeof (pre));

for (i = 1 ;i <= n;i ++ )
{
scanf(
" %d " , & c);
insert(start,i,c);
}
for (i = 1 ;i <= n;i ++ )
{
scanf(
" %d " , & c);
insert(i,end,c);
}
for (i = 0 ;i < m;i ++ )
{
scanf(
" %d%d%d " , & a, & b, & c);
_insert(a,b,c);
_insert(b,a,c);
}
}

bool bfs()
{
int i;
memset(level,
- 1 , sizeof (level));

queue
< int > Q;
Q.push(start);
level[start]
= 0 ;

while ( ! Q.empty())
{
int x = Q.front();
Q.pop();

for (i = pre[x];(i + 1 );i = com[i].next)
{
int y = com[i].to;
if (level[y] == - 1 && com[i].val)
{
level[y]
= level[x] + 1 ;
if (y == end)
return true ;
Q.push(y);
}
}
}

return false ;
}

int dfs( int x, int limit = inf)
{
int i;
if (x == end)
return limit;

int flow = 0 ,temp;
for (i = pre[x]; (i + 1 ) && limit; i = com[i].next)
{
int y = com[i].to;
if (com[i].val && level[y] == level[x] + 1 )
{
temp
= dfs(y,fmin(limit,com[i].val));

com[i].val
-= temp;
com[i
^ 1 ].val += temp;

flow
+= temp;
limit
-= temp;
}
}

return flow;
}

int dinic()
{
int max_flow = 0 ;
while (bfs())
{
max_flow
+= dfs(start);
}

return max_flow;
}

int main()
{
while (scanf( " %d%d " , & n, & m) != EOF)
{
Init();
int num = dinic();
printf(
" %d\n " ,num);
}
return 0 ;
}

 

hdu:2063 http://acm.hdu.edu.cn/showproblem.php?pid=2063

建图的时候从女生到男生有容量,而从男生到女生那里没有的。

ContractedBlock.gif ExpandedBlockStart.gif 代码
 
   
#include < iostream >
#include
< queue >
using namespace std;

#define fmax(a,b) (a>b?a:b)
#define fmin(a,b) (a<b?a:b)

const long maxn_point = 1100 ;
const long maxn_edge = 10005 ;
const long inf = 1e9;

int Index,k,m,n;
int start,end;

struct node
{
int to;
int val;
int next;
}people[maxn_edge];
int pre[maxn_point];
int level[maxn_point];

void _insert( int from, int to, int val)
{
people[Index].to
= to;
people[Index].val
= val;
people[Index].next
= pre[from];
pre[from]
= Index ++ ;
}

void insert( int from, int to, int val)
{
_insert(from,to,val);
_insert(to,from,
0 );
}

void Init()
{
int i,a,b;
Index
= 0 ;

scanf(
" %d%d " , & m, & n);
start
= 0 ,end = m + n + 1 ;

memset(pre,
- 1 , sizeof (pre));

for (i = 1 ;i <= m;i ++ )
{
insert(start,i,
1 );
}
for (i = 1 ;i <= n;i ++ )
{
insert(m
+ i,end, 1 );
}
while (k -- )
{
scanf(
" %d%d " , & a, & b);
_insert(a,m
+ b, 1 );
_insert(m
+ b,a, 0 );
}
}

bool bfs()
{
int i;
queue
< int > Q;

Q.push(start);
memset(level,
- 1 , sizeof (level));
level[start]
= 0 ;

while ( ! Q.empty())
{
int x = Q.front();
Q.pop();

for (i = pre[x];(i + 1 );i = people[i].next)
{
int y = people[i].to;
if (people[i].val && - 1 == level[y])
{
level[y]
= level[x] + 1 ;
if (y == end)
return true ;
Q.push(y);
}
}
}

return false ;
}

int dfs( int x, int limit = inf)
{
int i;
if (x == end)
return limit;

int flow = 0 ,temp;
for (i = pre[x]; (i + 1 ) && limit; i = people[i].next)
{
int y = people[i].to;
if (people[i].val && level[y] == level[x] + 1 && (temp = dfs(y,fmin(limit,people[i].val))))
{
people[i].val
-= temp;
people[i
^ 1 ].val += temp;

limit
-= temp;
flow
+= temp;
}
}

return flow;
}
int dinic()
{
int max_flow = 0 ;
while (bfs())
{
// while(flow = dfs(start))
// max_flow += flow;
max_flow += dfs(start);
}

return max_flow;
}

int main()
{
while (scanf( " %d " , & k) != EOF && k != 0 )
{
Init();
int num = dinic();
printf(
" %d\n " ,num);
}
return 0 ;
}

 

SAP算法:从傻崽blog里拉来的。先当模板用!

邻接矩阵:

ContractedBlock.gif ExpandedBlockStart.gif 代码
 
   
#define M 1000
int maze[M][M];
int gap[M],dis[M],pre[M],cur[M];
int sap( int s, int t, int nodenum) {
CC(cur,
0 );CC(dis, 0 );CC(gap, 0 );
int u = pre[s] = s,maxflow = 0 ,aug = - 1 ;
gap[
0 ] = nodenum;
while (dis[s] < nodenum) {
loop: FOR(v,cur[u],nodenum)
if (maze[u][v] && dis[u] == dis[v] + 1 ) {
checkmin(aug,maze[u][v]);
pre[v]
= u;
u
= cur[u] = v;
if (v == t) {
maxflow
+= aug;
for (u = pre[u];v != s;v = u,u = pre[u]) {
maze[u][v]
-= aug;
maze[v][u]
+= aug;
}
aug
= - 1 ;
}
goto loop;
}
int mindis = nodenum - 1 ;
FF(v,nodenum)
if (maze[u][v] && mindis > dis[v]) {
cur[u]
= v;
mindis
= dis[v];
}
if (( -- gap[dis[u]]) == 0 ) break ;
gap[dis[u]
= mindis + 1 ] ++ ;
u
= pre[u];
}
return maxflow;
}

邻接表:

ContractedBlock.gif ExpandedBlockStart.gif 代码
 
   
#define M 40010
int gap[M],dis[M],pre[M],cur[M];
int NE,NV;
int head[M];
struct Node{
int c,pos,next;
}E[
999999 ];
int sap( int s, int t) {
memset(dis,
0 , sizeof ( int ) * (NV + 1 ));
memset(gap,
0 , sizeof ( int ) * (NV + 1 ));
FF(i,NV) cur[i]
= head[i];
int u = pre[s] = s,maxflow = 0 ,aug = - 1 ;
gap[
0 ] = NV;
while (dis[s] < NV) {
loop:
for ( int & i = cur[u]; i != - 1 ; i = E[i].next) {
int v = E[i].pos;
if (E[i].c && dis[u] == dis[v] + 1 ) {
checkmin(aug,E[i].c);
pre[v]
= u;
u
= v;
if (v == t) {
maxflow
+= aug;
for (u = pre[u];v != s;v = u,u = pre[u]) {
E[cur[u]].c
-= aug;
E[cur[u]
^ 1 ].c += aug;
}
aug
= - 1 ;
}
goto loop;
}
}
int mindis = NV;
for ( int i = head[u]; i != - 1 ; i = E[i].next) {
int v = E[i].pos;
if (E[i].c && mindis > dis[v]) {
cur[u]
= i;
mindis
= dis[v];
}
}
if ( ( -- gap[dis[u]]) == 0 ) break ;
gap[ dis[u]
= mindis + 1 ] ++ ;
u
= pre[u];
}
return maxflow;
}
void Insert( int u, int v, int c, int cc = 0 ) {
E[NE].c
= c; E[NE].pos = v;
E[NE].next
= head[u]; head[u] = NE ++ ;
E[NE].c
= cc; E[NE].pos = u;
E[NE].next
= head[v]; head[v] = NE ++ ;
}

int main() {
FF(i,NV) head[i]
= - 1 ;
NE
= 0 ;
}
 
  
/* *** **** **** **** **** ****
网络中最小费用最大流
参数含义: n代表网络中的总节点数
net[][]代表剩余网络
cost[][]代表单位费用
path[]保存增广路径
ecost[]源点到各点的最短路
算法:初始最小费用和最大流均为,寻找单位费用最短路
在最短路中求出最大流,即为增广路,再修改剩余网络,直到无可增广路为止
返回值: 最小费用,最大流量
**** **** **** **** **** ***
*/
const int NMAX = 210 ;
int net[NMAX][NMAX], cost[NMAX][NMAX];
int path[NMAX], ecost[NMAX];
int n;
bool bellman_ford()
{
int i,j;
memset(path,
- 1 , sizeof (path));
fill(ecost, ecost
+ NMAX, INT_MAX);
ecost[
0 ] = 0 ;

bool flag = true ;
while (flag) {
flag
= false ;
for (i = 0 ;i <= n;i ++ ) {
if (ecost[i] == INT_MAX) {
continue ;
}
for (j = 0 ;j <= n;j ++ ) {
if (net[i][j] > 0 && ecost[i] + cost[i][j] < ecost[j]) {
flag
= true ;
ecost[j]
= ecost[i] + cost[i][j];
path[j]
= i;
}
}
}
}
return ecost[n] != INT_MAX;
}

int min_cost_max_flow()
{
int i,j;
int mincost = 0 , maxflow = 0 ;
while ( bellman_ford() ) {
int now = n;
int neck = INT_MAX;
while (now != 0 ) {
int pre = path[now];
neck
= min(neck, net[pre][now]);
now
= pre;
}
maxflow
+= neck;
now
= n;
while (now != 0 ) {
int pre = path[now];
net[pre][now]
-= neck;
net[now][pre]
+= neck;
cost[now][pre]
= - cost[pre][now];
mincost
+= cost[pre][now] * neck;
now
= pre;
}
}
return mincost;
}

转载于:https://www.cnblogs.com/silencExplode/archive/2010/12/20/1912189.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值