P4722【模板】最大流 加强版 / 预流推进 题解
Push-Relabel 预流推进算法
I have decided to write a tutorial on a topic not even Um_nik knows! (source)
与增广算法不同,预流推进会暂时忽略流的守恒性,让节点暂时储存流。
对节点定义一个「高度」
推送(Push)操作:对于边
重贴标签(Relabel)操作:对于节点
算法的流程就是先对
正确性证明:如果有点要将高度抬到
HLPP (Highest Label Preflow Push) 算法
与上文所述的「不断寻找溢出节点」略有不同,HLPP 算法每次都会寻找溢出节点中高度最高的进行操作(详见代码),其复杂度为
复杂度证明:
将算法分为 Push,Relabel,选择最高节点三部分进行复杂度分析。
每个节点最高的高度是
对于 Push 操作,我们定义一个 Push f操作是「饱和的」当且仅当 Push 操作执行完后,这条边满流,否则就是「不饱和的」. 饱和的 Push 的数量是
对于不饱和的 Push,我们定义一个溢出节点的「势能」是所有高度不超过自己(包括自己,包括非溢出节点)的节点个数,即
定义一个「阶段」是指在最高的溢出节点不发生改变时进行的所有操作的集合。一个阶段中每个节点只会产生一次不饱和的 Push(如果多于一次,那么这个节点一定是推了又被推从而溢出,说明有比最高溢出节点高的溢出节点,矛盾),一次 HLPP 的阶段数量是
在大阶段中高度为
对于寻找最高节点的操作,我们维护
总复杂度即为
复杂度证明相关参考资料:
Levent Tunqel, On the Complexity of Preflow-Push Algorithms for Maximum-Flow Problems(总述算法流程,复杂度证明篇幅较长)
Tarjan, Analysis of the Highest-Label Push-Relabel Algorithm(简要证明复杂度,但不完整)
Joseph Cheriyan and Kurt Mehlhorn, An Analysis of the Highest-Level Selection Rule in the
Preflow-Push Max-Flow Algorithm(简要且完整地证明了不饱和的Push的数量是
Andrew V. Goldberg and Robert E. Tarjan, A New Approach to the Maximum-Flow Problem(第 7-8 页证明饱和的Push的数量是
一些常数优化:
- BFS优化
即初始将所有点的高度都设为到
typedef long long ll;
ll s, t, ex[MAXN + 5] = {}, ht[MAXN + 5], hmx = 0; // 超额流,高度,处于该高度的节点的个数,最高高度
vector<Edge> e[MAXN + 5];
stack<ll> B[MAXN * 2 + 5]; // 有哪些溢出节点处于该高度
void addEdge(ll u, ll v, ll w) {
e[u].push_back(Edge(v, w, e[v].size()));
e[v].push_back(Edge(u, 0, e[u].size() - 1));
}
bool push(ll u, bool ini = 0) { // 如果处在初始化阶段,则忽略高度和超额流 返回推完之后是否仍然溢出
for (Edge& i : e[u]) {
if (i.val == 0 || (!ini && ht[i.to] != ht[u] - 1) || ht[i.to] == inf) {
continue;
}
ll ps = ini ? i.val : min(i.val, ex[u]); // 要推的流
if (i.to != s && i.to != t && ex[i.to] == 0) {
B[ht[i.to]].push(i.to);
hmx = max(hmx, ht[i.to]);
}
ex[i.to] += ps;
ex[u] -= ps;
i.val -= ps;
e[i.to][i.rev].val += ps;
if (ex[u] == 0) {
return 0;
}
}
return 1;
}
void relabel(ll u) {
ht[u] = inf;
for (Edge i : e[u]) {
if (i.val == 0) {
continue;
}
ht[u] = min(ht[u], ht[i.to] + 1);
}
if (ht[u] != inf) {
B[ht[u]].push(u);
hmx = max(hmx, ht[u]);
}
}
bool bfs_init() {
memset(ht, inf, sizeof ht);
ht[t] = 0;
queue<ll> q;
q.push(t);
while (!q.empty()) {
ll h = q.front();
q.pop();
for (Edge i : e[h]) {
if (ht[i.to] != inf || i.val != 0) { // 反向bfs,因此只走容量为0的边
continue;
}
q.push(i.to);
ht[i.to] = ht[h] + 1;
}
}
if (ht[s] == inf) {
return 0;
} else {
ht[s] = n;
return 1;
}
}
ll select() {
while (hmx >= 0 && B[hmx].empty()) {
hmx--;
}
if (hmx >= 0) {
ll ret = B[hmx].top();
B[hmx].pop();
return ret;
} else {
return -1;
}
}
ll maxFlow() {
if (!bfs_init()) { // s->t无路径,返回0
return 0;
}
push(s, 1); // 初始化
ll u;
while ((u = select()) != -1) {
if (push(u)) { // 仍然溢出,则relabel
relabel(u);
}
}
return ex[t];
}