基本知识: 流网络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
#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算法:
自己对着网上写的一个代码,还很挫,用空再改改:
#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;
}
用邻接表写的
#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
设置超级源点和超级汇点,建立流向图。最大流做法
#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
建图的时候从女生到男生有容量,而从男生到女生那里没有的。
#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里拉来的。先当模板用!
邻接矩阵:
#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;
}
邻接表:
#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;
}