2011.07.21


唔……不困,索性来搞各种流吧~反正明天也不想早起去什么实验室的……

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 的边构成残量网络 G,增广路径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}。

         Dinic 算法的基本步骤为:
       1) 计算残余网络的层次图。我们定义 h[i] 为顶点 i 距离源 S 所经过到最小边数,求出所有顶点的 h 值,h[] 值相同 的顶点属于同一层,这就是网络的层次图。
       2) 在层次图上进行 BFS 增广,直到不存在增广路径。这时求得的增广路径上顶点是分层的,路径上不可能存在两个顶点属于同一层,即 h[i]== h[j] (i!= j )。同时,求得层次图后,我们可以在层次图上进行多次增广。
      3) 重复 1 和 2。直到不存在增广路径。
      可知,Dinic 算法找到的增广路径是最短的,即经过的顶点数最少。再者,Dinic 算法找一条增广路径同时可以找到多条(这一点非常重要),类似增广路径树。比如我们找到了一条增广路径,这条增广路径所增加的流量为 C,则这条增广路径上必然有一条边残余容量为 C,这是我们不必又从起点开始寻找增广路,而是从 i 顶点出发找增广路,这样就减少了重复计算,提高了效率,这就是所说的多路增广
Dinic+邻接表模版

#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


你可能感兴趣的:(图论->网络流,图论,学习笔记,path,算法,网络,sap,buffer,insert)