最大流问题

一个小知识:重启控制台输入

在调试过程中,需要system("pause")一下,如果freopen了in.txt会发现无法pause。这需要重新启用控制台输入。

  • windows下:freopen("CON", "r", stdin)
  • linux下:freopen("/dev/console", "r", stdin)

Ford-Fulkerson算法:朴素思路

一开始,每条路径可用流量值都是题目输入的流量。
每次随机从源点走到终点,在这条路径上用瓶颈流量来更新这条路上所有边的可用流量值。
如此迭代,直到从源点无法到达终点。

Ford-Fulkerson算法是一种原理,它的基础是“最大流最小割”定理。
最大流最小割定理其实就是木桶原理、链条原理。链条中最薄弱的一环决定了链条的强度。
最大流最小割定理:一个s-t流的最大值,等于其s-t割的最小容量。

一个定理

对于一个网络流,如果每条边的最大流量值都是整数,那么整个网络的最大流量值为整数,并且每条边的实际流量也是整数。
这可以根据Ford-Fulkerson算法的求解过程推理而得,因为每次更新的都是一个整数值。

Edmond-Karp算法(最短增广路算法)

使用广搜算法,每次选取源点到终点的最短路径作为增广路。
复杂度分析:需要进行N(边数)次迭代,每次迭代都需要找到一条增广路,找一条增广路需要进行一次广搜,一次广搜的最差复杂度为O(N×M),即边数乘以点数。总的复杂度为O(N×M×N),即:边数×边数×点数
如下图,当终结点所在深度固定时,每次广搜都必须遍历全部结点,这是有最坏时间复杂度。并且,每次只能删除掉一条最小边(故需删除M(边数)次)

695653-20170307154337984-935665773.png

EK算法的最佳情况
695653-20170307155423047-1970782170.png

Dicnic算法(连续最短增广路算法)

深搜广搜相结合
Dicnic算法就是为了解决EK算法的最差情况而生的,然而对于EK算法的最佳情况,Dicnic显得多此一举。
Dinic算法的思想也是分阶段地在层次网络中增广。
它与最短增广路算法不同之处是:最短增广路每个阶段执行完一次BFS增广后,要重新启动BFS从源点Vs开始寻找另一条增广路;而在Dinic算法中,只需一次DFS过程就可以实现多次增广,这是Dinic算法的巧妙之处。
进行完一次BFS之后,终结点的深度就确定了,假设为k。使用DFS可以找到深度为k的全部增广路。每进行一次BFS,都会使得终结点深度变得更深。

Dicnic算法步骤

  1. 初始化容量网络和网络流
  2. 构造残留网络和层次网络,若汇点不在层次网络中则算法结束 输出最大流
  3. 在层次网络中用一次DFS进行增广,DFS执行完毕,该阶段的增广也就完毕了。
  4. 转至步骤2

其它算法

SAP算法、ISAP算法

一道裸题:hdu1532

本题是最大流问题最原始的形式 给定M个结点,N条有向边
此题需要注意

  • 多个样例,注意初始化数据,每次接受新输入之前清空旧数据
  • 有重边,这要求添加边时不能直接赋值,而要用+=

EK算法

#include<iostream>
#include<stdio.h> 
#include<list>
#include<string.h>
#include<queue>
#include<algorithm>
using namespace std;
int N, M;
const int maxn = 207;
list<int>g[maxn];
int flow[maxn][maxn];
int pre[maxn];
void bfs(){
    memset(pre, -1, sizeof(pre));
    queue<int>q;
    q.push(1);
    pre[1] = 0;
    while (q.empty() == false){
        int now = q.front();
        q.pop();
        for (list<int>::iterator i = g[now].begin(); i != g[now].end(); i++){
            int to = *i;
            if (pre[to] == -1 && flow[now][to] > 0){
                q.push(to);
                pre[to] = now;
                                if(to==M)return;//如果找到了终结点,直接返回
            }
        }
    }
}
int getMin(){
    int ans = 1e9;
    for (int i = M; i != 1; i = pre[i]){
        ans = min(ans, flow[pre[i]][i]);
    }
    return ans;
}
void update(int mi){
    for (int i = M; i != 1; i = pre[i]){
        flow[pre[i]][i] -= mi;
    }
}
int main(){
    freopen("in.txt", "r", stdin);
    while (cin >> N >> M){
        for (int i = 1; i <= M; i++)g[i].clear();
        memset(flow, 0, sizeof(flow));
        for (int i = 0; i < N; i++){
            int from, to, f;
            cin >> from >> to >> f;
            flow[from][to] += f;
            g[from].push_back(to);
        }
        int ans = 0;
        while (true){
            bfs();
            if (pre[M] == -1)break;
            int mi = getMin();
            update(mi);
            ans += mi;
        }
        cout << ans << endl;
    }
}

网上有此题代码,这份代码直接进行深搜,相当于dicnic算法没有BFS阶段,效率不知如何

#include <cstdio>  
#include <cstring>  
#include <iostream>  
#include <string>  
#include <algorithm>  
#include <map>  
#include <vector>  
using namespace std;  
const int N = 1100;  
const int INF = 0x3f3f3f3f;  
  
struct Node  
{  
    int to;//终点  
    int cap; //容量  
    int rev;  //反向边  
};  
  
vector<Node> v[N];  
bool used[N];  
  
void add_Node(int from,int to,int cap)  //重边情况不影响  
{  
    v[from].push_back((Node){to,cap,v[to].size()});  
    v[to].push_back((Node){from,0,v[from].size()-1});  
}  
  
int dfs(int s,int t,int f)  
{  
    if(s==t)  
        return f;  
    used[s]=true;  
    for(int i=0;i<v[s].size();i++)  
    {  
        Node &tmp = v[s][i];  //注意  
        if(used[tmp.to]==false && tmp.cap>0)  
        {  
            int d=dfs(tmp.to,t,min(f,tmp.cap));  
            if(d>0)  
            {  
                tmp.cap-=d;  
                v[tmp.to][tmp.rev].cap+=d;  
                return d;  
            }  
        }  
    }  
    return 0;  
}  
  
int max_flow(int s,int t)  
{  
    int flow=0;  
    for(;;){  
        memset(used,false,sizeof(used));  
        int f=dfs(s,t,INF);  
        if(f==0)  
            return flow;  
        flow+=f;  
    }  
}  
int main()  
{  
    int n,m;  
    while(~scanf("%d%d",&n,&m))  
    {  
        memset(v,0,sizeof(v));  
        for(int i=0;i<n;i++)  
        {  
            int x,y,z;  
            scanf("%d%d%d",&x,&y,&z);  
            add_Node(x,y,z);  
        }  
        printf("%d\n",max_flow(1,m));  
    }  
}  

Dicnic算法

#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算法

#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;  
}  

转载于:https://www.cnblogs.com/weiyinfu/p/6513963.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值