Как реализовать алгоритм Диница со слоистой сетью и блокирующим потоком?
Эдмондс-Карп проходит, но на больших тестах потока TLE. Все говорят писать Диница. Объясните идею слоистой сети (BFS-уровни) и блокирующего потока (DFS с указателями), и дайте корректную реализацию на C++ с асимптотикой.
2 ответа
Идея Диница. Алгоритм работает фазами. В каждой фазе:
- BFS от $s$ строит уровни
level[v]= кратчайшее расстояние в остаточной сети. Если до $t$ не дошли — поток максимален, стоп. - DFS ищет блокирующий поток в слоистой сети (идём только по рёбрам $level[v]+1 = level[to]$). «Блокирующий» = насыщаем пути так, что ни одного пути $s\to t$ по слоистым рёбрам не осталось.
Ключ к скорости — указатель ptr[v]: при DFS из вершины $v$ не перебираем заново уже исчерпанные рёбра, а двигаем указатель. Это превращает один DFS-проход фазы в $O(VE)$.
struct Edge{int to; long long cap;};
vector<Edge> e; vector<vector<int>> g;
int n; vector<int> level, ptr;
void add_edge(int u,int v,long long c){
g[u].push_back(e.size()); e.push_back({v,c});
g[v].push_back(e.size()); e.push_back({u,0});
}
bool bfs(int s,int t){
level.assign(n,-1); level[s]=0;
queue<int> q; q.push(s);
while(!q.empty()){int v=q.front();q.pop();
for(int id:g[v]) if(level[e[id].to]==-1 && e[id].cap>0){
level[e[id].to]=level[v]+1; q.push(e[id].to);}}
return level[t]!=-1;
}
long long dfs(int v,int t,long long pushed){
if(v==t||pushed==0) return pushed;
for(int &cid=ptr[v]; cid<(int)g[v].size(); ++cid){
int id=g[v][cid], to=e[id].to;
if(level[v]+1!=level[to]||e[id].cap==0) continue;
long long d=dfs(to,t,min(pushed,e[id].cap));
if(d>0){ e[id].cap-=d; e[id^1].cap+=d; return d; }
}
return 0;
}
long long maxflow(int s,int t){
long long flow=0;
while(bfs(s,t)){
ptr.assign(n,0);
while(long long pushed=dfs(s,t,LLONG_MAX)) flow+=pushed;
}
return flow;
}
Асимптотика: $O(V^2 E)$ в общем случае, $O(E\sqrt{E})$ на единичных пропускных (двудольное паросочетание!), $O(E\sqrt{V})$ для сетей с единичными capacity. Память $O(V+E)$. Берите long long для cap и flow.
Две частые ошибки в реализации Диница:
- Забыть
ptrи каждый DFS начинать перебор рёбер с нуля. Тогда блокирующий поток вырождается в $O(VE)$ на путь и общая оценка ломается — будет как обычный Форд-Фалкерсон.ptr[v]обязателен, и он обнуляется в начале каждой фазы, а не каждого DFS. - Идти по нелинейным уровням. Условие
level[v]+1 == level[to]строгое: ходим строго на следующий слой. Если ослабить доlevel[to] > level[v], можно зациклиться или потерять блокирующее свойство.
Ещё нюанс производительности: число фаз не больше $O(V)$, потому что после каждой фазы длина кратчайшего пути строго растёт. Это и даёт множитель $V$ в оценке.