Перейти до основного вмісту

Максимальний потік — алгоритм MPM

Алгоритм MPM (Malhotra, Pramodh-Kumar and Maheshwari) розв'язує задачу про максимальний потік за O(V3)O(V^3). Цей алгоритм схожий на алгоритм Дініца.

Коли підходить цей алгоритм?
  • Потрібен максимальний потік у щільному графі, де V3V^3 вигідніше за оцінку Дініца O(V2E)O(V^2 E)? (у загальному випадку зазвичай простіше брати Дініц)
  • Граф без вартостей на ребрах (інакше це задача про потік мінімальної вартості → потік мінімальної вартості)?

Алгоритм

Подібно до алгоритму Дініца, MPM працює фазами, і протягом кожної фази ми знаходимо блокуючий потік у шаруватій мережі залишкової мережі графа GG. Головна відмінність від Дініца — у тому, як ми знаходимо блокуючий потік. Розгляньмо шаруватую мережу LL. Для кожної вершини означимо її вхідний потенціал та вихідний потенціал так:

pin(v)=(u,v)L(c(u,v)f(u,v))pout(v)=(v,u)L(c(v,u)f(v,u))\begin{align} p_{in}(v) &= \sum\limits_{(u, v)\in L}(c(u, v) - f(u, v)) \\\\ p_{out}(v) &= \sum\limits_{(v, u)\in L}(c(v, u) - f(v, u)) \end{align}

Також покладемо pin(s)=pout(t)=p_{in}(s) = p_{out}(t) = \infty. Маючи pinp_{in} та poutp_{out}, означимо потенціал як p(v)=min(pin(v),pout(v))p(v) = min(p_{in}(v), p_{out}(v)). Вершину rr називаємо опорною вершиною, якщо p(r)=min{p(v)}p(r) = min\{p(v)\}. Розгляньмо опорну вершину rr. Стверджуємо, що потік можна збільшити на p(r)p(r) так, щоб p(r)p(r) стало рівним 00. Це справедливо, бо LL ациклічна, тож ми можемо проштовхнути потік із rr вихідними ребрами, і він досягне tt, оскільки кожна вершина має достатній вихідний потенціал, щоб проштовхнути потік далі, коли він до неї дійде. Аналогічно ми можемо притягнути потік із ss. Побудова блокуючого потоку ґрунтується на цьому факті. На кожній ітерації ми знаходимо опорну вершину і проштовхуємо потік від ss до tt через rr. Цей процес можна змоделювати за допомогою BFS. Усі повністю насичені дуги можна видалити з LL, оскільки вони все одно не використовуватимуться далі в цій фазі. Так само можна видалити всі вершини, відмінні від ss та tt, у яких немає вихідних або вхідних дуг.

Кожна фаза працює за O(V2)O(V^2), бо є щонайбільше VV ітерацій (адже принаймні обрана опорна вершина видаляється), і на кожній ітерації ми видаляємо всі ребра, через які пройшли, окрім щонайбільше VV. Підсумовуючи, отримуємо O(V2+E)=O(V2)O(V^2 + E) = O(V^2). Оскільки фаз менш ніж VV (див. доведення тут), MPM працює загалом за O(V3)O(V^3).

Реалізація

struct MPM{
struct FlowEdge{
int v, u;
long long cap, flow;
FlowEdge(){}
FlowEdge(int _v, int _u, long long _cap, long long _flow)
: v(_v), u(_u), cap(_cap), flow(_flow){}
FlowEdge(int _v, int _u, long long _cap)
: v(_v), u(_u), cap(_cap), flow(0ll){}
};
const long long flow_inf = 1e18;
vector<FlowEdge> edges;
vector<char> alive;
vector<long long> pin, pout;
vector<list<int> > in, out;
vector<vector<int> > adj;
vector<long long> ex;
int n, m = 0;
int s, t;
vector<int> level;
vector<int> q;
int qh, qt;
void resize(int _n){
n = _n;
ex.resize(n);
q.resize(n);
pin.resize(n);
pout.resize(n);
adj.resize(n);
level.resize(n);
in.resize(n);
out.resize(n);
}
MPM(){}
MPM(int _n, int _s, int _t){resize(_n); s = _s; t = _t;}
void add_edge(int v, int u, long long cap){
edges.push_back(FlowEdge(v, u, cap));
edges.push_back(FlowEdge(u, v, 0));
adj[v].push_back(m);
adj[u].push_back(m + 1);
m += 2;
}
bool bfs(){
while(qh < qt){
int v = q[qh++];
for(int id : adj[v]){
if(edges[id].cap - edges[id].flow < 1)continue;
if(level[edges[id].u] != -1)continue;
level[edges[id].u] = level[v] + 1;
q[qt++] = edges[id].u;
}
}
return level[t] != -1;
}
long long pot(int v){
return min(pin[v], pout[v]);
}
void remove_node(int v){
for(int i : in[v]){
int u = edges[i].v;
auto it = find(out[u].begin(), out[u].end(), i);
out[u].erase(it);
pout[u] -= edges[i].cap - edges[i].flow;
}
for(int i : out[v]){
int u = edges[i].u;
auto it = find(in[u].begin(), in[u].end(), i);
in[u].erase(it);
pin[u] -= edges[i].cap - edges[i].flow;
}
}
void push(int from, int to, long long f, bool forw){
qh = qt = 0;
ex.assign(n, 0);
ex[from] = f;
q[qt++] = from;
while(qh < qt){
int v = q[qh++];
if(v == to)
break;
long long must = ex[v];
auto it = forw ? out[v].begin() : in[v].begin();
while(true){
int u = forw ? edges[*it].u : edges[*it].v;
long long pushed = min(must, edges[*it].cap - edges[*it].flow);
if(pushed == 0)break;
if(forw){
pout[v] -= pushed;
pin[u] -= pushed;
}
else{
pin[v] -= pushed;
pout[u] -= pushed;
}
if(ex[u] == 0)
q[qt++] = u;
ex[u] += pushed;
edges[*it].flow += pushed;
edges[(*it)^1].flow -= pushed;
must -= pushed;
if(edges[*it].cap - edges[*it].flow == 0){
auto jt = it;
++jt;
if(forw){
in[u].erase(find(in[u].begin(), in[u].end(), *it));
out[v].erase(it);
}
else{
out[u].erase(find(out[u].begin(), out[u].end(), *it));
in[v].erase(it);
}
it = jt;
}
else break;
if(!must)break;
}
}
}
long long flow(){
long long ans = 0;
while(true){
pin.assign(n, 0);
pout.assign(n, 0);
level.assign(n, -1);
alive.assign(n, true);
level[s] = 0;
qh = 0; qt = 1;
q[0] = s;
if(!bfs())
break;
for(int i = 0; i < n; i++){
out[i].clear();
in[i].clear();
}
for(int i = 0; i < m; i++){
if(edges[i].cap - edges[i].flow == 0)
continue;
int v = edges[i].v, u = edges[i].u;
if(level[v] + 1 == level[u] && (level[u] < level[t] || u == t)){
in[u].push_back(i);
out[v].push_back(i);
pin[u] += edges[i].cap - edges[i].flow;
pout[v] += edges[i].cap - edges[i].flow;
}
}
pin[s] = pout[t] = flow_inf;
while(true){
int v = -1;
for(int i = 0; i < n; i++){
if(!alive[i])continue;
if(v == -1 || pot(i) < pot(v))
v = i;
}
if(v == -1)
break;
if(pot(v) == 0){
alive[v] = false;
remove_node(v);
continue;
}
long long f = pot(v);
ans += f;
push(v, s, f, false);
push(v, t, f, true);
alive[v] = false;
remove_node(v);
}
}
return ans;
}
};