目录
概念
网络流G=(V,E)是一个有向图,每条边都有一个非负的容量 ,如果
,那么
网络流中有两个特殊的点,源点s,汇点t
为了方便,我们假设G是连通图,并且
流:G的流是实值函数f,满足下面3个性质
容量限制:
反对称性:
流守恒性:
所以流的值定义为
用通俗点的话讲,就是,源点可以当作水库,边当作水管,然后将水流到汇点,问能流多
容量限制:流的量不能超过水管的大小
流守恒:简单来说就是除了s和t,流入多少就要流出多少,不能多不能少
流的值:源点流出了多少,或者汇点接收了多少,也可以叫做流量
容量网络:由容量组成的网络流,也就是原来的网络流
流量网络:由流组成的网络流
可行流:满足流的条件的网络流称之为可行流
最大流:可行流中,流量最大的
链:流量网络中从源点到汇点的路径
前向弧:链中,边与容量网络中方向一致的边
后向弧:链中,边与容量网络中方向相反的边
增广路:链中,前向弧的流量<=容量,后向弧流量>0
割:将网络流中,顶点分为2个集合
S-T割:割的两个集合S,T,,比如上面那个图,源点A,汇点C,则S={A,D},T={C,B}就是一个S-T割
割的容量:S-T割中,前向弧的容量和
最小割:割的容量中最小的
割的流量:S-T割中,前向弧的流量和-后向弧的流量和
还是上面那个图源点A,汇点C,S={A,D},T={C,B},
那割的容量就是c(A,B)+c(D,C)=4+8
割的流量就是f(A,B)+f(D,C)-f(D,B)=4+8-5
所以割的流量=割的容量,当且仅当S-T割中没有后向弧
最大流最小割定理
命题1:对于可行流的任意一个割,割的流量=可行流的流量
证明:
假设T={t},S=V-{t},显然可行流流量就是流入汇点的流量,割的流量也是,所以相等
每当我们从S中取出一个点加入T的时候,比如V5,那这时候会减去V5到T集合的流量,但是会加上到V5的流量,由于流量时守恒的,所以割的流量=可行流的流量
根据归纳,可以知道,这个命题成立
比如这张图,流量是19
第一次S={1,2,3,5},T={4,6},割的流量=12-4+7+4=19
第二次S={1,3,5},T={2,4,6},割的流量=11+1-4+7+4=19
命题2:可行流的流量一定小于等于任意一个割的容量
证明:可行流的流量=割的流量<=割的容量
命题3:对于可行流G,设其流量为f,如下三个命题等价:
1.存在一个割使得割的容量c=f
2.f是最大流的流量
3.G中不存在任何增广路
证明:
1推2: 假设c不是最小割,则存在c0<c,但是根据可行流的流量=割的流量<=割的容量,f<c0,也就是c=f<c0矛盾,所以c是最小割,又因为达到最小割时,割的流量=割的容量
可行流的流量为最大流
2推3:逆否命题:G中存在增广路,f不是最大流的流量
如果存在增广路,则f可以继续增大,显然不是最大流
3推1:
假设不存在增广路了,设S为源点s通过非饱和前向弧(这条前向弧流量<容量)或者非零反向弧能到达的点,T=V-S,这构成了一个S-T割,,如果
,则有
这条边为非饱和前向弧或者非零反向弧,那v就属于S,与v属于T矛盾,于是命题得证
没特殊说明,代码根据洛谷P3376写的
Ford-Fulkerson
准确来说,这是一个方法不是具体的算法
主要思路,找增广路,更新边,但是这个方法并没有规定怎么找增广路
如果你用dfs,时间复杂度O((V+E)*maxflow)
dfs 邻接矩阵
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 205;
const long long int MAX = 0x7FFFFFFFFFFFFFFF;
long long int edge[N][N];//邻接矩阵
bool visit[N];
int n, s, t;//总数,源点,汇点
/**
* 深度优先
* @param u 当前点
* @param flow 能通过的流量
* @return 返回流量,无法到达汇点则返回0
*/
long long int dfs(int u, long long int flow) {
if (u == t)return flow;
visit[u] = true;
for (int i = 1; i <= n; ++i) {
if (visit[i] || 0 == edge[u][i])continue;//访问过,或者没有残余流量
long long int result = dfs(i, edge[u][i] < flow ? edge[u][i] : flow);
if (result > 0) {
//正向边
edge[u][i] -= result;
//反向边
edge[i][u] += result;
return result;
}
}
return 0;
}
/**
* 最大流
* @return 最大流
*/
long long int Ford_Fulkerson() {
if (s == t)return 0;
long long int result = 0;
long long int ans = dfs(s, MAX);
while (ans > 0) {
result += ans;
memset(visit, false, sizeof(visit));
ans = dfs(s, MAX);
}
return result;
}
int main() {
int m;
scanf("%d%d%d%d", &n, &m, &s, &t);
while (m--) {
int u, v;
long long int c;
scanf("%d%d%lld", &u, &v, &c);
edge[u][v] += c;
}
printf("%lld\n", Ford_Fulkerson());
return 0;
}
dfs 前向星
我写的好像有点问题,小心T掉
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 205, M = 10005;
const long long int MAX = 0x7FFFFFFFFFFFFFFF;
//前向星
struct Edge {
int v, next;
long long int c;
}edge[M];
int head[N];//前向星头
bool visit[N];
int n, s, t, cnt = 1;
/**
* 前向星添加边
* @param u 起点
* @param v 终点
* @param c 容量
*/
void add_edge(int u, int v, long long int c) {
++cnt;
edge[cnt].v = v;
edge[cnt].next = head[u];
edge[cnt].c = c;
head[u] = cnt;
}
long long int dfs(int u, long long int flow) {
if (u == t)return flow;
visit[u] = true;
for (int i = head[u]; i != 0; i = edge[i].next) {
int v = edge[i].v;
long long int c = edge[i].c;
if (visit[v] || 0 == c)continue;
long long int result = dfs(v, c < flow ? c : flow);
if (result > 0) {
edge[i].c -= result;
edge[i ^ 1].c += result;
return result;
}
}
return 0;
}
long long int Ford_Fulkerson() {
if (s == t)return 0;
long long int result = 0;
long long int ans = dfs(s, MAX);
while (ans > 0) {
result += ans;
memset(visit, false, sizeof(visit));
ans = dfs(s, MAX);
}
return result;
}
int main() {
int m;
scanf("%d%d%d%d", &n, &m, &s, &t);
while (m--) {
int u, v;
long long int c;
scanf("%d%d%lld", &u, &v, &c);
add_edge(u, v, c);
add_edge(v, u, 0);
}
printf("%lld\n", Ford_Fulkerson());
return 0;
}
Edmond-Karp
Ford-Fulkerson找增广路的时候,用bfs
这里用前向星
读图的时候,需要直接加入反向边,优点是,反向边只需要异或1就可以获得
比如正向是10,反向边就是11
时间复杂度O(VE^2)
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
typedef long long int LL;
const int N = 205, M = 10005;
const int MAX=0x7fffffff;
//前向星
struct Edge {
int v, next, c;
}edge[M];
int head[N];//前向星头
bool visit[N];
int pre[N];
int n, s, t, cnt = 1;
/**
* 前向星添加边
* @param u 起点
* @param v 终点
* @param c 容量
*/
void add_edge(int u, int v, int c) {
++cnt;
edge[cnt].v = v;
edge[cnt].next = head[u];
edge[cnt].c = c;
head[u] = cnt;
}
bool bfs() {
queue<int> q;
q.push(s);
memset(visit, false, sizeof(visit));
visit[s] = true;
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = head[u]; i != 0; i = edge[i].next) {
int v = edge[i].v;
int c = edge[i].c;
if (visit[v] || 0 == c)continue;
pre[v] = i;//记录前驱对应的边
if (v == t)return true;
q.push(v);
visit[v] = true;
}
}
return false;
}
LL Edmond_Karp() {
if (s == t)return 0;
LL result = 0;
while (bfs()) {
int x = t;
int ans = MAX;
while (x != s) {
int i = pre[x];
if (edge[i].c < ans)ans = edge[i].c;
x = edge[i ^ 1].v;//从反向边找前驱
}
x = t;
//更新
while (x != s) {
int i = pre[x];
edge[i].c -= ans;
edge[i ^ 1].c += ans;
x = edge[i ^ 1].v;
}
result += ans;
}
return result;
}
int main() {
int m;
scanf("%d%d%d%d", &n, &m, &s, &t);
while (m--) {
int u, v, c;
scanf("%d%d%d", &u, &v, &c);
add_edge(u, v, c);
add_edge(v, u, 0);
}
printf("%lld\n", Edmond_Karp());
return 0;
}
Dinic算法
时间复杂度:上界O(V^2 E),不过据说这个上界很松
简单来说就是
bfs分层,然后dfs去多路增广,增广时,只从层数低到高(这个就好比你知道目标在东边,你只会走正东,东北,东南,其他方向不会让你更靠近目标)
有几个优化:
1.送到汇点的流量=0时,不再遍历这个点,也叫做炸点优化
2.当前弧:就是前几个弧其实已经满流了,下次从还没有满流的点开始遍历
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
typedef long long int LL;
const int N = 205, M = 10005;
const int MAX = 0x7fffffff;
struct Edge {
int v, next, c;
}edge[M];
int head[N];
int cnt = 1;
int now[N];//当前弧
int level[N];//分层
void add_edge(int u, int v, int c) {
++cnt;
edge[cnt].v = v;
edge[cnt].c = c;
edge[cnt].next = head[u];
head[u] = cnt;
}
int s, t, n;
/**
* 广度优先,分层
* @return true到达汇点|false没到达汇点
*/
bool bfs() {
memset(level, 0, sizeof(level));
queue<int> q;
q.push(s);
level[s] = 1;
now[s] = head[s];
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = head[u]; i != 0; i = edge[i].next) {
int v = edge[i].v;
int c = edge[i].c;
if (level[v] > 0 || 0 == c)continue;
level[v] = level[u] + 1;
now[v] = head[v];
if (v == t)return true;
q.push(v);
}
}
return false;
}
/**
* 深度优先,多路增广
* @param u 当前点
* @param flow 流量
* @return 到汇点的流量
*/
int dfs(int u, int flow) {
if (u == t)return flow;
int out = 0;//当前点流到汇点的量
for (int i = now[u]; i != 0 && flow > 0; i = edge[i].next) {//从当前弧开始遍历
now[u] = i;//更新当前弧
int v = edge[i].v;
int c = edge[i].c;
if (c == 0 || level[v] != level[u] + 1)continue;
int ans = dfs(v, c < flow ? c : flow);
if (ans == 0) {
level[v] = 0;//前面到达不了汇点,所以把level设成0,代表以后都不走这个点
continue;
}
edge[i].c -= ans;
edge[i ^ 1].c += ans;
out += ans;
flow -= ans;//减少当前容量
}
return out;
}
LL Dinic() {
if (s == t)return 0;
LL result = 0;
while (bfs()) {
result += dfs(s, MAX);
}
return result;
}
int main() {
int m;
scanf("%d%d%d%d", &n, &m, &s, &t);
while (m--) {
int u, v, c;
scanf("%d%d%d", &u, &v, &c);
add_edge(u, v, c);
add_edge(v, u, 0);
}
printf("%lld\n", Dinic());
return 0;
}
ISAP
时间复杂度:上界O(V^2 E),不过据说这个上界很松
前面的dinic是多次bfs,这个isap就只用一次bfs
isap先是从汇点反向bfs,标记了每个点到汇点的距离dis(所以这里一定要存反向边,不然就无法遍历了)
然后就一直dfs,只走dis[u]=dis[v]+1,因为这样可以走最短的增广路
如果某一个点u出去的点都走完了,但是还有剩余的流,就将距离min{dis[v]}+1
gap优化,当dis[i]=k的个数为0,说明断层了,因为你只能走dis[u]=dis[v]+1的,也就是连续的点,如果不连续,说明源点和汇点不连通了,就可以退出了,把源点距离改为顶点个数(因为有向无环图距离最多为顶点个数-1)
当前弧:和刚刚的dinic里一样
递归+多路增广
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
typedef long long int LL;
const int N = 205, M = 10005;
const int MAX = 0x7fffffff;
struct Edge {
int v, next, c;
}edge[M];
int head[N], cnt = 1;
int dis[N];//到汇点距离
int cur[N];//当前弧
int gap[N];//gap[i]表示到汇点距离为i的个数
void add_edge(int u, int v, int c) {
++cnt;
edge[cnt].v = v;
edge[cnt].c = c;
edge[cnt].next = head[u];
head[u] = cnt;
}
int n, s, t;
/**
* bfs 反向遍历
*/
void bfs() {
memset(dis, -1, sizeof(dis));
// memset(gap,0,sizeof(gap));
dis[t] = 0;
gap[0] = 1;
queue<int> q;
q.push(t);
while (!q.empty()) {
int u = q.front();
q.pop();
cur[u] = head[u];
for (int i = head[u]; i != 0; i = edge[i].next) {
int v = edge[i].v;
if (dis[v] != -1)continue;//遍历过了,跳过
dis[v] = dis[u] + 1;
++gap[dis[v]];
q.push(v);
}
}
}
int dfs(int u, int flow) {
if (u == t)return flow;
int out = 0;
for (int i = cur[u]; i != 0; i = edge[i].next) {
cur[u] = i;
int v = edge[i].v;
int c = edge[i].c;
if (0 == c || dis[u] != dis[v] + 1)continue;
int ans = dfs(v, c < flow ? c : flow);
if (ans > 0) {
out += ans;
flow -= ans;
edge[i].c -= ans;
edge[i ^ 1].c += ans;
}
if (0 == flow || dis[s] == n)return out;
}
//到这,u出去的所有边都遍历过了,但是还有没用完的流
--gap[dis[u]];//修改距离
if (0 == gap[dis[u]]) {//断层了
dis[s] = n;
return out;
}
++dis[u];
++gap[dis[u]];
cur[u] = head[u];//重置当前弧
return out;
}
LL ISAP() {
if (s == t)return 0;
bfs();
LL result = 0;
while (dis[s] < n) {
result += dfs(s, MAX);
}
return result;
}
int main() {
int m;
scanf("%d%d%d%d", &n, &m, &s, &t);
while (m--) {
int u, v, c;
scanf("%d%d%d", &u, &v, &c);
add_edge(u, v, c);
add_edge(v, u, 0);
}
printf("%lld\n", ISAP());
return 0;
}
非递归+单路增广
其实非递归也很好写,但是我写的是单路增广,多路增广好像不太好写,然后非递归需要记录一下前驱
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
typedef long long int LL;
const int N = 205, M = 10005;
const int MAX = 0x7fffffff;
struct Edge {
int v, next, c;
}edge[M];
int head[N], cnt = 1;
int dis[N];//到汇点距离
int cur[N];//当前弧
int gap[N];//gap[i]表示到汇点距离为i的个数
int pre[N];//前驱
void add_edge(int u, int v, int c) {
++cnt;
edge[cnt].v = v;
edge[cnt].c = c;
edge[cnt].next = head[u];
head[u] = cnt;
}
int n, s, t;
/**
* bfs 反向遍历
*/
void bfs() {
memset(dis, -1, sizeof(dis));
// memset(gap,0,sizeof(gap));
dis[t] = 0;
gap[0] = 1;
queue<int> q;
q.push(t);
while (!q.empty()) {
int u = q.front();
q.pop();
cur[u] = head[u];
for (int i = head[u]; i != 0; i = edge[i].next) {
int v = edge[i].v;
if (dis[v] != -1)continue;//遍历过了,跳过
dis[v] = dis[u] + 1;
++gap[dis[v]];
q.push(v);
}
}
}
/**
* 找到增广路之后调整这一路的流量
* @return 这条增广路送出去的流量
*/
int augment() {
int u = t;
int result = MAX;
//计算流量
while (u != s) {
int i = pre[u];
int c = edge[i].c;
if (c < result)result = c;
u = edge[i ^ 1].v;
}
u = t;
//调整
while (u != s) {
int i = pre[u];
edge[i].c -= result;
edge[i ^ 1].c += result;
u = edge[i ^ 1].v;
}
return result;
}
LL ISAP() {
if (s == t)return 0;
LL result = 0;
bfs();
int u = s;
while (dis[s] < n) {
if (u == t) {//找到增广路
result += augment();
u = s;//从源点继续找
}
bool flag = false;
//找下一个可以走的点
for (int i = cur[u]; i != 0; i = edge[i].next) {
int v = edge[i].v;
int c = edge[i].c;
if (0 == c || dis[u] != dis[v] + 1)continue;
pre[v] = i;//记录前驱
cur[u] = i;//当前弧
u = v;//下一个
flag = true;
break;
}
if (!flag) {//走到这说明没有可以走的点,需要调整到汇点的距离为min{dis[v]}+1
int min_dis = n;
for (int i = head[u]; i != 0; i = edge[i].next) {
int v = edge[i].v;
if (edge[i].c > 0 && dis[v] < min_dis)min_dis = dis[v];
}
--gap[dis[u]];
if (gap[dis[u]] == 0) {//gap优化
break;
}
dis[u] = min_dis + 1;
++gap[dis[u]];
cur[u] = head[u];
if (u != s) {//回溯上一个点
u = edge[pre[u] ^ 1].v;
}
}
}
return result;
}
int main() {
int m;
scanf("%d%d%d%d", &n, &m, &s, &t);
while (m--) {
int u, v, c;
scanf("%d%d%d", &u, &v, &c);
add_edge(u, v, c);
add_edge(v, u, 0);
}
printf("%lld\n", ISAP());
return 0;
}
HLPP
又叫最高标号预留推进
时间复杂度
简单来说就是把节点看做水库,然后将水流屯起来一起送出去
为了防止你推给我,我推给你,推到TLE,我们要有一个高度,类似isap
水往低处流,所以,我们只允许高的往低的流
如果能流的边都流过一遍了,但是还有剩余流量,就抬高这个节点,让他继续流
所以算法流程
1.bfs,初始化高度,汇点为0,然后反向bfs
2.建立一个优先队列,针对顶点的高度
3.对于起点,单独处理,把他的出边都流一遍,把对应的顶点都加入优先队列
4.从优先队列出一个点,对于高度比他低1的点推流,如果这些点不在队列,就加入队列(算法中的push)
5.如果刚刚那个顶点还有剩余流量,我们就修改高度,改成他出边顶点中最低的顶点的高度+1,然后再入队
6.重复4-5,直到队空
gap优化,和isap类似,如果这个高度没有了,我们就把高于这个顶点且小于n+1的顶点,高度设置为n+1,让他尽快把流量流回源点
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
typedef long long int LL;
const int N = 205, M = 10005;
const int MAX = 0x3f3f3f3f;
struct Edge {
int v, next, c;
} edge[M];
int head[N], cnt = 1;
int h[N];//每个点高度
LL e[N];//每个点的容量
int gap[N * 2];//这个高度完全有可能到2N-1,所以,开2倍
bool inq[N];//在队列里
struct cmp {
bool operator()(int left, int right)const {
return h[left] < h[right];
}
};
priority_queue<int, vector<int>, cmp> pq;
void add_edge(int u, int v, int c) {
++cnt;
edge[cnt].v = v;
edge[cnt].c = c;
edge[cnt].next = head[u];
head[u] = cnt;
}
int n, s, t;
/**
* 反向遍历
* @return 能否到达源点
*/
bool bfs() {
memset(h, 0x3f, sizeof(h));
// memset(e,0,sizeof(e));
// memset(gap,0,sizeof(gap));
// memset(inq,false,sizeof(inq));
queue<int> q;
q.push(t);
h[t] = 0;
gap[0] = 1;
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = head[u]; i != 0; i = edge[i].next) {
int v = edge[i].v;
int c = edge[i ^ 1].c;
if (0 == c || h[v] <= h[u] + 1)
continue;
h[v] = h[u] + 1;
++gap[h[v]];
q.push(v);
}
}
return h[s] != MAX;
}
/**
* 推流
* @param u 推流点
*/
void push(int u) {
for (int i = head[u]; i != 0; i = edge[i].next) {
int v = edge[i].v;
int c = edge[i].c;
if (0 == c || h[u] != h[v] + 1)continue;
int flow = c < e[u] ? c : e[u];
edge[i].c -= flow;
edge[i ^ 1].c += flow;
e[u] -= flow;
e[v] += flow;
if (v != s && v != t && !inq[v]) {
inq[v] = true;
pq.push(v);
}
if (0 == e[u])return;
}
}
/**
* 重贴标签,抬高u的高度,使得刚好能流向想一个节点
* @param u 重贴标签的点
*/
void relabel(int u) {
h[u] = MAX;
for (int i = head[u]; i != 0; i = edge[i].next) {
int v = edge[i].v;
if (0 == edge[i].c || h[v] + 1 >= h[u])continue;
h[u] = h[v] + 1;
}
}
LL HLPP() {
if (s == t)return 0;
if (!bfs())return 0;
h[s] = n;
//起点单独走
for (int i = head[s]; i != 0; i = edge[i].next) {
int v = edge[i].v;
int c = edge[i].c;
if (0 == c)continue;
edge[i].c = 0;
edge[i ^ 1].c = c;
e[s] -= c;
e[v] += c;
if (v != s && v != t && !inq[v]) {
pq.push(v);
inq[v] = true;
}
}
while (!pq.empty()) {
int u = pq.top();
pq.pop();
inq[u] = false;
push(u);
if (e[u] != 0) {//还有流量
--gap[h[u]];
if (0 == gap[h[u]]) {
//把高于这个顶点的,高度改成n+1,让他们把流量流回源点
for (int i = 1; i <= n; ++i) {
if (i != s && i != t && h[i] > h[u] && h[i] < n + 1) {
h[i] = n + 1;
}
}
}
relabel(u);
++gap[h[u]];
pq.push(u);
inq[u] = true;
}
}
return e[t];
}
int main() {
int m;
scanf("%d%d%d%d", &n, &m, &s, &t);
while (m--) {
int u, v, c;
scanf("%d%d%d", &u, &v, &c);
add_edge(u, v, c);
add_edge(v, u, 0);
}
printf("%lld\n", HLPP());
return 0;
}
终极优化
主要针对洛谷P4722,以及loj127
优化1:桶排代替优先队列,需要记录一下当前最高的,然后就可以桶排了,就每一个高度开一个桶
优化2:断层后,删除大于等于那个高度的顶点,反正也流不到汇点
优化3:开vector和list这种,开了O2就挺快的,这种时候邻接表找反向边需要额外记录反向边的位置
优化4:原始版本每次推流结束都要更新一次。然而实际上推流过程中就可以进行更新标签。因此只需要刚开始就设置好初始高度,此后在玄学的push里顺便更新就行了
优化5:快读(我没写,你可以写一下)
#include<cstdio>
#include<vector>
#include<list>
const int N = 1205;
const int INF = 0x7fffffff;
struct Edge {
int v;//邻接点
int c;//流量
int nxt;//反向边
Edge(int v, int c, int nxt) :v(v), c(c), nxt(nxt) {}
};
std::vector<Edge> edge[N];//邻接表
std::vector<int> h;//顶点高度
std::vector<int> gap;//每个高度的个数
std::vector<int> bucket[N];//桶排
std::vector<int> e;//每个顶点的剩余流量
std::list<int> lst[N];//高度为i的顶点
std::vector<std::list<int>::iterator> it;//顶点在list中的位置
int cur_max_height;//当前最高高度
int cur_height;//当前高度
int n, s, t;
/**
* 添加边
* @param u 顶点
* @param v 顶点
* @param c 流量
*/
void add_edge(int u, int v, int c) {
edge[u].push_back(Edge(v, c, edge[v].size()));
edge[v].push_back(Edge(u, 0, edge[u].size() - 1));
}
/**
* 全局重贴标签
*/
void global_relabel() {
h.assign(n + 1, n);
gap.assign(n + 1, 0);
h[t] = 0;
std::vector<int> q(n + 1);//队列
int front = 0, rear = 0;
q[rear++] = t;
while (front != rear) {
int u = q[front++];
for (std::vector<Edge>::iterator p = edge[u].begin(); p != edge[u].end(); ++p) {
int v = p->v;
int c = edge[v][p->nxt].c;
if (h[v] != n || 0 == c)continue;
h[v] = h[u] + 1;
++gap[h[v]];
q[rear++] = v;
}
}
for (int i = 1; i <= n; ++i) {
if (h[i] >= n)continue;
/*
* 相当于
* lst[i].push_front(i);
* it[i]=lst[i].begin()
*/
it[i] = lst[h[i]].insert(lst[h[i]].begin(), i);//加入高度为h[i]的点击
if (e[i] > 0) {//有剩余流量,加入桶排
bucket[h[i]].push_back(i);
}
}
cur_height = cur_max_height = h[q[rear - 1]];
}
/**
* 根据边推流
* @param u 顶点
* @param ed 推流
*/
void push(int u, Edge& ed) {
int v = ed.v;
int flow = std::min(e[u], ed.c);
ed.c -= flow;
edge[v][ed.nxt].c += flow;
e[u] -= flow;
e[v] += flow;
if (e[v] > 0 && e[v] <= flow) {//防止起点,终点
bucket[h[v]].push_back(v);//加入桶排
}
}
/**
* 推流
* @param u 顶点
*/
void push(int u) {
int relabel_height = n;
int temp_height = h[u];
for (std::vector<Edge>::iterator p = edge[u].begin(); p != edge[u].end(); ++p) {
if (0 == p->c)continue;
if (h[u] == h[p->v] + 1) {
push(u, *p);
if (0 == e[u])return;
}
else {
relabel_height = std::min(h[p->v] + 1, relabel_height);
}
}
if (1 == gap[temp_height]) {//gap优化
for (int i = temp_height; i <= cur_max_height; ++i) {
for (std::list<int>::iterator p = lst[i].begin(); p != lst[i].end(); ++p) {
--gap[h[*p]];
h[*p] = n;
}
lst[i].clear();
cur_max_height = temp_height - 1;
}
}
else {//重贴标签
--gap[temp_height];
lst[temp_height].erase(it[u]);
h[u] = relabel_height;
if (relabel_height == n)return;
//抬高了高度
++gap[relabel_height];
it[u] = lst[relabel_height].insert(lst[relabel_height].begin(), u);
cur_height = relabel_height;//修改当前高度,因为高度变高了
cur_max_height = std::max(cur_max_height, cur_height);
bucket[relabel_height].push_back(u);
}
}
int HLPP() {
if (s == t)return 0;
// cur_max_height=cur_height=0;
// h[s]=n;
it.resize(n + 1);
// for(int i=1;i<=n;++i){
// if(i==s)continue;
// it[i]=lst[h[i]].insert(lst[h[i]].begin(),i);
// }
// gap.assign(n+1,0);
// gap[0]=n-1;
e.assign(n + 1, 0);
//防止超出INF
e[s] = INF;
e[t] = -INF;
for (std::vector<Edge>::iterator p = edge[s].begin(); p != edge[s].end(); ++p) {
int v = p->v;
int flow = p->c;
p->c = 0;
edge[v][p->nxt].c += flow;
e[s] -= flow;
e[v] += flow;
}
global_relabel();
if (h[s] == 0)return 0;
while (cur_height != 0) {
if (bucket[cur_height].empty()) {
--cur_height;
}
else {
int u = bucket[cur_height].back();
bucket[cur_height].pop_back();
push(u);
}
}
return e[t] + INF;
}
int main() {
int m;
scanf("%d%d%d%d", &n, &m, &s, &t);
while (m--) {
int u, v, c;
scanf("%d%d%d", &u, &v, &c);
add_edge(u, v, c);
}
printf("%d\n", HLPP());
return 0;
}