POJ 1273
题意:赤果果的最大流,n个点m条边,求1到n的最大流,题解见下模版。
最大流算法:
最大流算法分为两个大类,较为常见的是基于增广路 (Augmenting Path) 和压入和重标号 (Push Relabel),也有结合了两类有点的如ISAP(见下文)。
先说说增广路算法吧~所有增广路的鼻祖都是Ford - Fulkerson方法,具体来说就是找到一条可增广路就增广直到找不到为止,不过这个主要作为一种思想。假设有向网络 G 中边 (i,j) 的容量为 c(i,j) ,当前流量为 f(i,j) ,则此边的剩余流量即为 r(i,j) = c(i,j) - f(i,j) ,其反向边的剩余流量为 r(j,i) = f(i,j) 。有向网中所有剩余流量 r(i,j) > 0 的边构成残量网络 Gf ,增广路径p即是残量网络中从源点 s 到终点 t 的路径。沿路径 p 增广流量 f 的操作基本都是相同的,各算法的区别就在于寻找增广路径 p 的方法不同。例如可以寻找从 s 到 t 的最短路径,或者流量最大的路径。
增广路和最大流最小割定理有着天然而美妙的联系。说下最大流最小割定理,把一个流网络的顶点集划分成两个集合S和T,使得源点s ∈S且汇点t ∈T,割(S,T)的容量C(S,T) =∑Cuv, 其中u∈S且v∈T。从直观上看,截集(S,T)是从源点s到汇点t的必经之路,如果该路堵塞则流从s无法到达t。于是我们可以得到下面的定理:任意一个流网络的最大流量等于该网络的最小的割的容量。证明参见:http://bbs.lzjtu.edu.cn/bbsanc.php?path=%2Fgroups%2Fsci.faq%2FComputer%2FProgramOld%2FAlgorithm%2F4%2F5%2F26.txt
基于该定理的Edmonds-Karp就是应用之一。E-K的思路就是利用BFS每次寻找一条最短路径的增广路,然后按路径增广。复杂度为 O(VE2),实际情况中应用较少,但为我们进一步提供了思路。
EK:广搜找最短路,基本的增光路算法。
#include
#include
using namespace std;
const int INF=1000000000;
const int MAX=300;
queueq;
int cap[MAX][MAX]; //容量
int flow[MAX][MAX]; //流量
int a[MAX]; //a[i]=j:源点s到i的最小残量为j
int p[MAX]; //p[v]=u:v的父结点为u
int N,M; //N:边数 M:点数
int f; //记录总的流量
//求s->t的最大流
void Edmonds_Karp(int s,int t)
{
int u,v;
memset(flow,0,sizeof(flow));
f=0;
while (true)
{
memset(a,0,sizeof(a));
a[s]=INF;
q.push(s);
while (!q.empty()) //Bfs找增广路
{
u=q.front();
q.pop();
for ( v=1;v<=M;v++)
{
if (!a[v] && cap[u][v]>flow[u][v]) //找到新结点v
{
p[v]=u; //记录v的父亲
q.push(v);
(a[u] < cap[u][v]-flow[u][v]) ? a[v]=a[u] : a[v]=cap[u][v]-flow[u][v]; //s->v路径上的最小残量
}
}
}
if (a[t] == 0) //找不到增广路,则当前已是最大流
{
break;
}
//更新正向流量和反向流量
for (u=t;u!=s;u=p[u])
{
flow[p[u]][u]+=a[t];
flow[u][p[u]]-=a[t];
}
f+=a[t]; //更新从s流出的总流量
}
printf("%d\n",f);
}
int main()
{
int i,j;
int x,y,w;
while (scanf("%d%d",&N,&M) != EOF)
{
memset(cap,0,sizeof(cap));
for (i=1;i<=N;i++)
{
scanf("%d%d%d",&x,&y,&w);
cap[x][y]+=w; //处理重边
}
Edmonds_Karp(1,M);
}
return 0;
}
同属SAP系的Dinic是效率较为高的算法,结合了BFS和DFS的优势,时间复杂度是O{V^2*E}。
#include
#include
using namespace std;
const long maxn=300;
const long maxm=300000;
const long inf=0x7fffffff;
struct node
{
long v,next;
long val;
}s[maxm*2];
long level[maxn],p[maxn],que[maxn],out[maxn],ind;
void init()
{
ind=0;
memset(p,-1,sizeof(p));
}
inline void insert(long x,long y,long z)
{
s[ind].v=y;
s[ind].val=z;
s[ind].next=p[x];
p[x]=ind++;
s[ind].v=x;
s[ind].val=0;
s[ind].next=p[y];
p[y]=ind++;
}
long max_flow(long n,long source,long sink)
{
long ret=0;
long h=0,r=0;
while(1){
long i;
for(i=0;i=0){
que[++q]=cur;
out[source]=s[cur].next;
}
else
break;
}
long u=s[que[q]].v;
if(u==sink){
long dd=inf;
long index=-1;
for(i=0;i<=q;i++){
if(dd>s[que[i]].val){
dd=s[que[i]].val;
index=i;
}
}
ret+=dd;
for(i=0;i<=q;i++){
s[que[i]].val-=dd;
s[que[i]^1].val+=dd;
}
for(i=0;i<=q;i++){
if(s[que[i]].val==0){
q=index-1;
break;
}
}
}
else{
long cur=out[u];
for(;cur!=-1;cur=s[cur].next){
if(s[cur].val&&out[s[cur].v]!=-1&&level[u]+1==level[s[cur].v])
break;
}
if(cur!=-1){
que[++q]=cur;
out[u]=s[cur].next;
}
else{
out[u]=-1;
q--;
}
}
}
}
return ret;
}
long m,n;
int main()
{
while(scanf("%ld %ld",&m,&n)!=EOF){
init();
for(int i=0;i
Dinic+邻接矩阵(by DD_engi)
void Dinic()
{
for(;;){
BFS();
if(D[T]==-1)break;
int path_n=0;
int x=S;
memcpy(cur,E,sizeof(cur));
for(;;){
if(x==T){
int mink=-1,delta=INT_MAX;
for(int i=0;icc;
mink=i;
}
}
for(int i=0;ic-=delta;
path[i]->back->c+=delta;
}
path_n=mink;
x=path[path_n]->x;
}
edge* e;
for(e=cur[x];e;e=e->next){
if(e->c==0)
continue;
int y=e->y;
if(D[x]+1==D[y])
break;
}
cur[x]=e;
if(e){
path[path_n++]=e;
x=e->y;
}
else{
if(path_n==0)
break;
D[x]=-1;
--path_n;
x=path[path_n]->x;
}
}
}
}
ISAP:
ISAP(improved SAP)如上文所说引入了预推类的特点,与 Dinic 算法不同,ISAP 中的距离标号是每个顶点到达终点 t 的距离。同样也不需显式构造分层网络,只要保存每个顶点的距离标号即可。程序开始时用一个反向 BFS 初始化所有顶点的距离标号,之后从源点开始,进行如下三种操作:
(1)当前顶点 i 为终点时增广
(2) 当前顶点有满足 dist[i] = dist[j] + 1 的出弧时前进
(3) 当前顶点无满足条件的出弧时重标号并回退一步。整个循环当源点 s 的距离标号 dist[s] >= n 时结束。对 i 点的重标号操作可概括为 dist[i] = 1 + min{dist[j] : (i,j)属于残量网络Gf}。
里面一个很厉害的优化是GAP:由于从 s 到 t 的一条最短路径的顶点距离标号单调递减,且相邻顶点标号差严格等于1,因此可以预见如果在当前网络中距离标号为 k (0 <= k < n) 的顶点数为 0,那么可以知道一定不存在一条从 s 到 t 的增广路径,此时可直接跳出主循环。而BFS那一步则优化略少。
ISAP+GAP+BFS+双链表
#include
#include
using namespace std;
const int maxnode = 1024;
const int infinity = 2100000000;
struct edge{
int ver; // vertex
int cap; // capacity
int flow; // current flow in this arc
edge *next; // next arc
edge *rev; // reverse arc
edge(){}
edge(int Vertex, int Capacity, edge *Next)
:ver(Vertex), cap(Capacity), flow(0), next(Next), rev((edge*)NULL){}
void* operator new(size_t, void *p){
return p;
}
}*Net[maxnode];
int dist[maxnode]= {0}, numbs[maxnode] = {0}, src, des, n;
void rev_BFS(){
int Q[maxnode], head = 0, tail = 0;
for(int i=1; i<=n; ++i){
dist[i] = maxnode;
numbs[i] = 0;
}
Q[tail++] = des;
dist[des] = 0;
numbs[0] = 1;
while(head != tail){
int v = Q[head++];
for(edge *e = Net[v]; e; e = e->next){
if(e->rev->cap == 0 || dist[e->ver] < maxnode)continue;
dist[e->ver] = dist[v] + 1;
++numbs[dist[e->ver]];
Q[tail++] = e->ver;
}
}
}
int maxflow(){
int u, totalflow = 0;
edge *CurEdge[maxnode], *revpath[maxnode];
for(int i=1; i<=n; ++i)CurEdge[i] = Net[i];
u = src;
while(dist[src] < n){
if(u == des){ // find an augmenting path
int augflow = infinity;
for(int i = src; i != des; i = CurEdge[i]->ver)
augflow = min(augflow, CurEdge[i]->cap);
for(int i = src; i != des; i = CurEdge[i]->ver){
CurEdge[i]->cap -= augflow;
CurEdge[i]->rev->cap += augflow;
CurEdge[i]->flow += augflow;
CurEdge[i]->rev->flow -= augflow;
}
totalflow += augflow;
u = src;
}
edge *e;
for(e = CurEdge[u]; e; e = e->next)
if(e->cap > 0 && dist[u] == dist[e->ver] + 1)break;
if(e){ // find an admissible arc, then Advance
CurEdge[u] = e;
revpath[e->ver] = e->rev;
u = e->ver;
} else { // no admissible arc, then relabel this vertex
if(0 == (--numbs[dist[u]]))break; // GAP cut, Important!
CurEdge[u] = Net[u];
int mindist = n;
for(edge *te = Net[u]; te; te = te->next)
if(te->cap > 0)mindist = min(mindist, dist[te->ver]);
dist[u] = mindist + 1;
++numbs[dist[u]];
if(u != src)
u = revpath[u]->ver; // Backtrack
}
}
return totalflow;
}
int main(){
int m, u, v, w;
freopen("ditch.in", "r", stdin);
freopen("ditch.out", "w", stdout);
while(scanf("%d%d", &m, &n) != EOF){ // POJ 1273 need this while loop
memset(dist,0,sizeof(dist));
memset(numbs,0,sizeof(numbs));
memset(Net,0,sizeof(Net));
edge *buffer = new edge[2*m];
edge *data = buffer;
src = 1; des = n;
while(m--){
scanf("%d%d%d", &u, &v, &w);
Net[u] = new((void*) data++) edge(v, w, Net[u]);
Net[v] = new((void*) data++) edge(u, 0, Net[v]);
Net[u]->rev = Net[v];
Net[v]->rev = Net[u];
}
rev_BFS();
printf("%d\n", maxflow());
delete [] buffer;
}
return 0;
}
还有这货自称ISAP,让100行的同学情何以堪……明天研究下看:我真的不信ISAP能写31行ORZ……
#include
#include
#define MAXN 20002
int g[MAXN][MAXN];//,c[MAXN][MAXN],f[MAXN][MAXN];
int m,n;
int vd[MAXN],d[MAXN];
inline min(int a,int b){return a 0 && d[u] == d[v] + 1) //如果是允许弧
{
tmp = dfs(v,min(flow-ret,g[u][v])); //如果找到,增广
g[u][v] -= tmp;
g[v][u] += tmp;
ret += tmp;
if(ret == flow)return ret;
}
if(d[1] >= n) return ret; //如果源点的标号距离大于n,即不存在增广路,这一步放在重标号之前
vd[d[u]]--;
if(vd[d[u]] == 0)d[1] = n; //如果该标号距离的顶点是唯一的,那么删除后图出现断层
d[u]++;
vd[d[u]]++;
return ret;
}
int main()
{
int i,x,y,z,flow,ans = 0;
scanf("%d%d",&m,&n);
for(i=1;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&z);
g[x][y] += z;
//c[x][y] += z;
}
vd[0] = n;
while(d[1] < n)
{
flow = dfs(1,INT_MAX);
ans += flow;
}
printf("max flow=%d\n",ans);
return 0;
}
证明见:http://www.topcoder.com/tc?module=Static&d1=tutorials&d2=maxFlowRevisited
这里有做出几个SAP类算法的效率比较,十分优秀:http://dantvt.is-programmer.com/tag/Dinic
还有就是非SAP类的最高标号预流推进HLPP ,号称是更快的最大流算法,明天看下吧~
http://hi.baidu.com/%B2%DD%B4%CC%E2%AC_sp/blog/item/0a6529b7a7f66dc436d3cae3.html