EK算法笔记

算法思想

这里就不赘述网络流的相关概念,之后会出一篇网络流总结来具体阐述

增广路算法

增广路算法是对给定的网络求得最大流的一种思想,它并不是具体的算法,其余的最大流算法是增广路算法的具体实现与演变

增广路定理:设flow是网络G的一个可行流,若不存在从源点到汇点的增广路,则flow是G的一个最大流

增广路算法的基本思想:在残余网络中找增广路,然后在实流网络中沿增广路增流,在残余网络中沿增广路减流,重复直到不存在增广路为止,最后根据定理,求得的实流网络的可行流即最大流

EK

EK算法属于增广路算法的一种实现,用来求解最大流

EK算法的基本思路基于BFS,不如说只是在多次BFS上加上了回溯与更新,使用队列存放已访问但未回溯的点,使用数组标记访问与增广路的前驱

算法步骤

  1. 初始化各必要变量值
  2. 将源点入队,标记源点访问
  3. 判断队列是否为空,空则结束,根据当前实流网络返回最大流值
  4. 获取队首,在残余网络中检查队首邻接点,若邻接点 i i i未访问,则置访问标记,前驱,如果 i = = t i==t i==t,表示已经找到一条增广路,跳转5,否则 i i i入队,执行3
  5. 从汇点开始,通过前驱数组在残余网络中逆向找增广路上每条边值的最小化值
  6. 更新实流网络和残余网络(增流和减流),更新最大流,跳2

找增广路
在混合网络中BFS,从源点s开始,搜索s的邻接点v,若未被访问,则标记已访问且记录v的前驱为u,若v不为汇点,则v入队,继续BFS,当v为汇点,则认为找到一条增广路,队列空则无增广路

代码

struct node {
    int next,to,cap,flow;//链式前向星,容量和流
} e[maxn];
bool BFS(int s,int t) {
    memset(pre,-1,sizeof(pre));//初始化
    memset(vis,0,sizeof(vis));
    queue<int>q;
    q.push(s);
    vis[s]=1;
    while(!q.empty()) {
        int u=q.front();
        q.pop();
        for(int i=head[u]; ~i; i=e[i].next) {
            int v=e[i].to;
            if(!vis[v]&&e[i].cap>e[i].flow) {//容量大于流,可增流
                vis[v]=1;
                pre[v]=i;
                q.push(v);
                if(v==t)return 1;
            }
        }
    }
    return 0;
}

增流
在找到一条增广路之后,根据前驱数组从汇点向前一直到源点,找增广路边上的 m i n min min,将该值作为增广量,然后从汇点到源点逐个修改,增广路同向边增加,反向边减小
代码

int EK(int s,int t) {
    int acc=0;
    while(BFS(s,t)) {
        int d=0x3f3f3f3f,v=t;
        while(v!=s) {
            int i=pre[v];
            d=min(d,e[i].cap-e[i].flow);//可增加的流量
            v=e[i^1].to;//换点
        }
        acc+=d;
        v=t;
        while(v!=s) {
            int i=pre[v];
            e[i].flow+=d;
            e[i^1].flow-=d;//实流
            v=e[i^1].to;
        }
    }
    return acc;
}

设边数为 E E E,点数为 V V V,在EK算法过程中,找到一条增广路的时间为 O ( E ) O(E) O(E),最多执行 O ( V E ) O(VE) O(VE)次,可以计算出时间复杂度为 O ( V E 2 ) O(VE^2) O(VE2)

训练

HDU3549

题目大意:给一个有向带权图,求解最大流

思路:直接使用EK即可

代码

#include <bits/stdc++.h>
const int maxn=1e4+10;
using namespace std;
int head[maxn],cnt,pre[maxn],T,n,m;
bool vis[maxn];
struct node {
    int next,to,cap,flow;//链式前向星,容量和流
} e[maxn];
void Add(int from,int to,int cap,int flow) {
    e[cnt].next=head[from];
    e[cnt].cap=cap;
    e[cnt].flow=flow;
    e[cnt].to=to;
    head[from]=cnt++;//注意是从0开始存的
}
bool BFS(int s,int t) {
    memset(pre,-1,sizeof(pre));//初始化
    memset(vis,0,sizeof(vis));
    queue<int>q;
    q.push(s);
    vis[s]=1;
    while(!q.empty()) {
        int u=q.front();
        q.pop();
        for(int i=head[u]; ~i; i=e[i].next) {
            int v=e[i].to;
            if(!vis[v]&&e[i].cap>e[i].flow) {//容量大于流,可增流
                vis[v]=1;
                pre[v]=i;//pre记录的是前驱边
                q.push(v);
                if(v==t)return 1;
            }
        }
    }
    return 0;
}
int EK(int s,int t) {
    int acc=0;
    while(BFS(s,t)) {
        int d=0x3f3f3f3f,v=t;
        while(v!=s) {
            int i=pre[v];
            d=min(d,e[i].cap-e[i].flow);//可增加的流量
            v=e[i^1].to;//换点
        }
        acc+=d;
        v=t;
        while(v!=s) {
            int i=pre[v];
            e[i].flow+=d;
            e[i^1].flow-=d;//实流
            v=e[i^1].to;
        }
    }
    return acc;
}
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >>T;
    for(int i=1; i<=T; i++) {
        memset(head,-1,sizeof(head));
        cnt=0;
        cin >>n>>m;
        while(m--) {
            int x,y,c;
            cin >>x>>y>>c;
            Add(x,y,c,0);
            Add(y,x,0,0);//存反边,容量和流均为0
        }
        cout <<"Case "<<i<<": ";
        cout <<EK(1,n)<<endl;
    }
    return 0;
}
/*
10
6 9
1 2 12
1 3 10
3 2 2
4 3 5
2 4 8
3 5 13
5 4 6
5 6 4
4 6 18
*/

HDU1532

题目大意:略

思路:EK模板题

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn=1e3;
int head[maxn],cnt,pre[maxn],n,m;
bool vis[maxn];
struct node {
    int next,to,cap,flow;
} e[maxn];
void Add(int from,int to,int cap,int flow) {
    e[cnt].to=to;
    e[cnt].next=head[from];
    e[cnt].flow=flow;
    e[cnt].cap=cap;
    head[from]=cnt++;
}
bool BFS(int s,int t) {
    memset(pre,-1,sizeof(pre));
    memset(vis,0,sizeof(vis));
    queue<int>q;
    vis[s]=1;
    q.push(s);
    while(!q.empty()) {
        int u=q.front();
        q.pop();
        for(int i=head[u]; ~i; i=e[i].next) {
            int v=e[i].to;
            if(!vis[v]&&e[i].cap>e[i].flow) {
                vis[v]=1;
                pre[v]=i;
                q.push(v);
                if(v==t)return 1;
            }
        }
    }
    return 0;
}
int EK(int s,int t) {
    int acc=0;
    while(BFS(s,t)) {
        int v=t,d=0x3f3f3f3f;
        while(v!=s) {
            int i=pre[v];
            d=min(d,e[i].cap-e[i].flow);
            v=e[i^1].to;
        }
        acc+=d;
        v=t;
        while(v!=s) {
            int i=pre[v];
            e[i].flow+=d;
            e[i^1].flow-=d;
            v=e[i^1].to;
        }
    }
    return acc;
}
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    while(cin >>n>>m) {
        memset(head,-1,sizeof(head));
        cnt=0;
        while(n--) {
            int s,e,c;
            cin >>s>>e>>c;
            Add(s,e,c,0);
            Add(e,s,0,0);
        }
        cout <<EK(1,m)<<endl;
    }
    return 0;
}

POJ1149

题目大意:M个猪圈,每个猪圈有初始数量,依次来N个人,每个人可以打开指定的猪圈,购买若干头猪,每个人有一个购买上限,每个人购买完后,猪圈不会立刻关上,管理员可以重新分配已打开的猪圈的猪,分配完后关闭猪圈等待下一个人,求出最多能卖出多少猪

思路:可以看到,如果没有购买次序之间的限制,每个猪圈都可以视为源点,客户设置为汇点,那么可以用一个超级汇点作为所有用户最后的汇点,容量为购买数量,在考虑购买次序之间的限制的情况下,设置一个源点,将源点和每个猪圈的第一个人连边,容量为开始时猪圈猪的数量,因为第一个人才能打开这个猪圈,使得猪圈内猪的数量能发生变化,若两个人先后打开一个相同的猪圈,则连边两人编号,容量为无穷大,因为两人能使猪圈内猪的数量变化

代码

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int maxn=1e4;
const int inf=0x3f3f3f3f;
int head[maxn],cnt,pre[maxn],n,m,init[maxn],last[maxn];
bool vis[maxn];
struct node {
    int to,next,cap,flow;
} e[maxn];
void Add(int from,int to,int cap,int flow) {
    e[cnt].to=to;
    e[cnt].next=head[from];
    e[cnt].cap=cap;
    e[cnt].flow=flow;
    head[from]=cnt++;
}
bool BFS(int s,int t) {
    memset(pre,-1,sizeof(pre));
    memset(vis,0,sizeof(vis));
    queue<int>q;
    q.push(s);
    vis[s]=1;
    while(!q.empty()) {
        int u=q.front();
        q.pop();
        for(int i=head[u]; ~i; i=e[i].next) {
            int v=e[i].to;
            if(!vis[v]&&e[i].cap>e[i].flow) {
                vis[v]=1;
                pre[v]=i;
                q.push(v);
                if(v==t)return 1;
            }
        }
    }
    return 0;
}
int EK(int s,int t) {
    int acc=0;
    while(BFS(s,t)) {
        int v=t,d=inf;
        while(v!=s) {
            int i=pre[v];
            d=min(d,e[i].cap-e[i].flow);
            v=e[i^1].to;
        }
        acc+=d;
        v=t;
        while(v!=s) {
            int i=pre[v];
            e[i].flow+=d;
            e[i^1].flow-=d;
            v=e[i^1].to;
        }
    }
    return acc;
}
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >>m>>n;
    for(int i=1; i<=m; i++)cin >>init[i];
    memset(head,-1,sizeof(head));
    for(int i=1; i<=n; i++) {
        int a,b;
        cin >>a;
        while(a--) {
            int k;
            cin >>k;
            if(last[k]==0) {//第一次访问
                last[k]=i;//记录上一层访问的客户
                Add(0,i,init[k],0);//与源点相连
                Add(i,0,0,0);
            } else {
                Add(last[k],i,inf,0);
                Add(i,last[k],0,0);
                last[k]=i;//更新上一个访问者
            }
        }
        cin >>b;
        Add(i,n+1,b,0);//需求与汇点相连
        Add(n+1,i,0,0);
    }
    cout <<EK(0,n+1);
    return 0;
}

总结

EK算法是用基于BFS和回溯的最大流算法,它适用于稀疏图,当边较多时,时间消耗会过大,这时应该选择别的算法,对于图论相关问题,如果算法已经确定的情况下,其实求解问题更重要的是如何建图

参考文献

  1. POJ1149最大网络流
  2. 《算法训练营 海量图解+竞赛刷题》
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值