网络流 dicnic sap 2种算法详细解释 以及例题POJ1459

http://blog.sina.com.cn/s/blog_691ce2b701016jfv.html

http://blog.sina.com.cn/s/blog_691ce2b70101843h.html


题目大意:
      给几个发电站,给几个消耗站,再给几个转发点。
      发电站只发电,消耗站只消耗电,转发点只是转发电,再给各个传送线的传电能力。
      问你消耗站能获得的最多电是多少

思路:建立一个超级源点和超级汇点,把发电站相连起来,消耗站连起来 然后就是模版的力量了

在此也讲了下dinic的原理:
求最大流的本质,就是不停的寻找增广路径。直到找不到增广路径为止。
对于这个一般性的过程,Dinic算法的优化如下:

(1)
Dinic算法首先对图进行一次BFS,然后在BFS生成的层次图中进行多次DFS。
层次图的意思就是,只有在BFS树中深度相差1的节点才是连接的。
这就切断了原有的图中的许多不必要的连接。很牛逼!
这是需要证明的,估计证明也很复杂。

(2)
除此之外,每次DFS完后,会找到路径中容量最小的一条边。
在这条边之前的路径的容量是大于等于这条边的容量的。
那么从这条边之前的点,可能引发出别的增广路径。
比如说 

S -> b -> c-> d -> T 是一条增广路径,容量最小的边是 b-> c。
可能存在一条 S -> b -> e ->f -> g -> T 这样的增广路径。
这样的话,在找到第一条增广路径后,只需要回溯到 b 点,就可以继续找下去了。
这样做的好处是,避免了找到一条路径就从头开始寻找另外一条的开销。
也就是再次从 S 寻找到 b 的开销。
这个过程看似复杂,但是代码实现起来很优雅,因为它的本质就是回溯!
(3)
在同一次 DFS 中。如果从一个点引发不出任何的增广路径,就将这个点在层次图中抹去。

#include <stdio.h>
#include <string.h>
#define VM 2000
#define EM 205500
#define inf 0x3f3f3f3f
struct Edge
{
    int frm,to,cap,next;
}edge[EM];

int head[VM],dep[VM],ep;     //dep为点的层次
void addedge (int cu,int cv,int cw)  //第一条边下标必须为偶数
{
    edge[ep].frm = cu;
    edge[ep].to = cv;
    edge[ep].cap = cw;
    edge[ep].next = head[cu];
    head[cu] = ep;
    ep ++;
    edge[ep].frm = cv;
    edge[ep].to = cu;
    edge[ep].cap = 0;
    edge[ep].next = head[cv];
    head[cv] = ep;
    ep ++;
}

int BFS (int src,int des)     //求出层次图
{
    int que[VM],i,front = 0,rear = 0;
    memset (dep,-1,sizeof(dep));
    que[rear++] = src;
    dep[src] = 0;
    while (front != rear)
    {
        int u = que[front++];
        front = front%VM;
        for (i = head[u];i != -1;i = edge[i].next)
        {
            int v = edge[i].to;
            if (edge[i].cap > 0&&dep[v] == -1) //容量大于0&&未在dep中
            {
                dep[v] = dep[u] + 1;        //建立层次图
                que[rear ++] = v;
                rear = rear % VM;
                if (v == des)  //找到汇点 返回
                    return 1;
            }
        }
    }
    return 0;
}
int dinic (int src,int des)
{
    int i,res = 0,top;
    int stack[VM];    //stack为栈,存储当前增广路
    int cur[VM];        //存储当前点的后继 跟head是一样的
    while (BFS(src,des))   //if BFS找到增广路
    {
        memcpy (cur,head,sizeof (head));
        int u = src;       //u为当前结点
        top = 0;
        while (1)
        {
            if (u == des)     //增广路已全部进栈
            {
                int min = inf,loc ;
                for (i = 0;i < top;i ++)       //找最小的增广跟并loc记录其在stack中位置
                    if (min > edge[stack[i]].cap)  //以便退回该边继续DFS
                    {
                        min = edge[stack[i]].cap;
                        loc = i;
                    }
                for (i = 0;i < top;i ++)   //偶数^1 相当加1 奇数^1相当减1 当正向边 = 0&&路径不合适时,正加负减
                {                           //偶数是正向边,奇数是负向边,边从0开始
                    edge[stack[i]].cap -= min;
                    edge[stack[i]^1].cap += min;
                }                              //将增广路中的所有边修改
                res += min;
                top = loc;
                u = edge[stack[top]].frm;         //当前结点修改为最小边的起点
            }
            for (i = cur[u];i != -1;cur[u] = i = edge[i].next)   //找到当前结点对应的下一条边
                if (edge[i].cap != 0&&dep[u] + 1 == dep[edge[i].to])//不满足条件时,修改cur值(去掉不合适的占)eg:1-->2 1-->3 1-->4 有边 但只有
                    break;                                  // 1-->4 这条边满足条件 就把1到2、3的边给去掉
            if (cur[u] != -1)            //当前结点的下一条边存在
            {
                stack[top ++] = cur[u];   //把该边放入栈中
                u = edge[cur[u]].to;         //再从下个点开始找
            }
            else
            {
                if (top == 0)        //当前结点无未遍历的下一条边且栈空,DFS找不到下一条增广路
                    break;
                dep[u] = -1;            //当前结点不在增广路中,剔除该点
                u = edge[stack[--top]].frm; //退栈 回朔,继续查找
            }
        }
    }
    return res;
}
int main ()///坐标从0或1开始均可  注意别忘记下面的2个初始化
{
    int np,nc,m,v1,v2,w,n;
    int src,des;
    char str[20];
    while (scanf ("%d%d%d%d",&n,&np,&nc,&m)!=EOF)
    {
        ep = 0;//边的初始化
        src = n;
        des = n+1;
        memset (head,-1,sizeof(head));///这里初始化
        while (m --)
        {
            scanf ("%s",str);
            sscanf (str,"(%d,%d)%d",&v1,&v2,&w);
            addedge (v1,v2,w);
        }
        while (np --)
        {
            scanf ("%s",str);
            sscanf (str,"(%d)%d",&v2,&w);
            addedge (src,v2,w);
        }
        while (nc--)
        {
            scanf ("%s",str);
            sscanf (str,"(%d)%d",&v1,&w);
            addedge (v1,des,w);
        }
        int ans = dinic (src,des);
        printf ("%d\n",ans);
    }
    return 0;
}



sap 算法  注意上面的n有用


#include <stdio.h>
#include <string.h>
const int VM = 110, EM = 20500, inf = 0x3f3f3f3f;
struct Edge
{
    int to, frm, nxt, cap;
}edge[EM];

int head[VM], ep, n, src, des;
int dep[VM], gap[VM]; //gap[x]=y:说明残留网络中dep[i]=x的个数为y

void addedge(int u, int v, int c)
{
    edge[ep].frm = u;
    edge[ep].to = v;
    edge[ep].cap = c;
    edge[ep].nxt = head[u];
    head[u] = ep++;
    edge[ep].frm = v;
    edge[ep].to = u;
    edge[ep].cap = 0;
    edge[ep].nxt = head[v];
    head[v] = ep++;
}

void BFS()
{
    memset(dep, -1, sizeof(dep));
    memset(gap, 0, sizeof(gap));
    gap[0] = 1;   //说明此时有1个dep[i] = 0
    int que[VM], front = 0, rear = 0;
    dep[des] = 0;
    que[rear++] = des;
    int u, v;
    while (front != rear)
    {
        u = que[front++];
        front = front%VM;
        for (int i=head[u]; i!=-1; i=edge[i].nxt)
        {
            v = edge[i].to;
            if (edge[i].cap != 0 || dep[v] != -1)
                continue;
            que[rear++] = v;
            rear = rear % VM;
            ++gap[dep[v] = dep[u] + 1];  //求出各层次的数量
        }
    }
}

int Sap()
{
    int res = 0;
    BFS();
    int cur[VM];
    int stack[VM], top = 0;
    memcpy(cur, head, sizeof(head));
    int u = src, i;
    while (dep[src] < n)
    {
        if (u == des)
        {
            int temp = inf, inser = n;
            for (i=0; i!=top; ++i)
                if (temp > edge[stack[i]].cap)
                {
                    temp = edge[stack[i]].cap;
                    inser = i;
                }
            for (i=0; i!=top; ++i)
            {
                edge[stack[i]].cap -= temp;
                edge[stack[i]^1].cap += temp;
            }
            res += temp;
            top = inser;
            u = edge[stack[top]].frm;
        }

        if (dep[u] != 0 && gap[dep[u] -1] == 0)//出现断层,无增广路
            break;
        for (i = cur[u]; i != -1; i = edge[i].nxt)//遍历与u相连的未遍历结点
            if (dep[edge[i].to] != -1)
                if (edge[i].cap != 0 && dep[u] == dep[edge[i].to] + 1) //层序关系, 找到允许
                    break;

        if (i != -1)//找到允许弧
        {
            cur[u] = i;
            stack[top++] = i;//加入路径栈
            u = edge[i].to;//查找下一个结点
        }
        else   //无允许的路径,修改标号 当前点的标号比与之相连的点中最小的多1
        {
            int min = n;
            for (i = head[u]; i != -1; i = edge[i].nxt) //找到与u相连的v中dep[v]最小的点
            {
                if (edge[i].cap == 0)
                    continue;
                if (min > dep[edge[i].to])
                {
                    min = dep[edge[i].to];
                    cur[u] = i;          //最小标号就是最新的允许弧
                }
            }
            --gap[dep[u]];          //dep[u] 的个数变化了 所以修改gap
            ++gap[dep[u] = min + 1]; //将dep[u]设为min(dep[v]) + 1, 同时修改相应的gap[]
            if (u != src) //该点非源点&&以u开始的允许弧不存在,退点
                u = edge[stack[--top]].frm;
        }
    }
    return res;
}

int main()///坐标从0开始
{
    int i, np, nc, m;
    char str[10];
    while (scanf("%d %d %d %d", &n, &np, &nc, &m) != EOF)
    {
        ep = 0;
        memset(head, -1, sizeof(head));
        int u, v, c;
        src = n, des = n + 1;
        n += 2;
        for (i=0; i!=m; ++i)
        {
            scanf("%stack", str);
            sscanf(str, "(%d,%d)%d", &u, &v, &c);
            addedge(u, v, c);
        }
        for (i=0; i!=np; ++i)
        {
            scanf("%stack", str);
            sscanf(str, "(%d)%d", &v, &c);
            addedge(src, v, c);
        }
        for (i=0; i!=nc; ++i)
        {
            scanf("%stack", str);
            sscanf(str, "(%d)%d", &u, &c);
            addedge(u, des, c);
        }
        printf("%d\n", Sap());
    }
    return 0;
}



你可能感兴趣的:(网络流 dicnic sap 2种算法详细解释 以及例题POJ1459)