題目鏈接: P3376 【模板】網絡最大流
Dinic
Dinic算法是網絡流最大流的優化算法之一,每一步對原圖進行分層,然後用DFS求增廣路。時間複雜度是O(n^2*m),Dinic算法最多被分爲n個階段,每個階段包括建層次網絡和尋找增廣路兩部分。
Dinic算法的思想是分階段地在層次網絡中增廣。它與最短增廣路算法不同之處是:最短增廣路每個階段執行完一次BFS增廣後,要重新啓動BFS從源點Vs開始尋找另一條增廣路;而在Dinic算法中,只需一次DFS過程就可以實現多次增廣。
層次圖:
層次圖,就是把原圖中的點按照點到源的距離分“層”,只保留不同層之間的邊的圖。
算法流程:
1、根據殘量網絡計算層次圖。
2、在層次圖中使用DFS進行增廣直到不存在增廣路。
3、重複以上步驟直到無法增廣。
實現
首先對每條弧存一條反向弧,初始流量爲0,當正向弧剩餘流量減少時,反向弧剩餘流量隨之增加,這樣就爲每條弧提供了一個反悔的機會,可以讓一個流沿反向弧退回而去尋找更優的路線。對於一個網絡流圖,用bfs將圖分層,只保留每個點到下一個層次的弧,目的是減少尋找增廣路的代價。對於每一次可行的增廣操作,用dfs的方法尋找一條由源點到匯點的路徑並獲得這條路徑的流量c。根據這條路徑修改整個圖,將所經之處正向邊流量減少c,反向邊流量增加c。如此反覆直到bfs找不到可行的增廣路線。
當前弧優化:
對於一個節點x,當它在dfs中走到了第i條弧時,前i-1條弧到匯點的流一定已經被流滿而沒有可行的路線了。那麼當下一次再訪問x節點的時候,前i-1條弧就可以被刪掉而沒有任何意義了。所以我們可以在每次枚舉節點x所連的弧時,改變枚舉的起點,這樣就可以刪除起點以前的所有弧以達到優化的效果。
本題增強了數據,時間更是卡到了500ms,以前的好多題解都過不去了。
有一個問題是我使用弧優化最後兩個點跑了750ms T 了,但是我把弧優化去掉了以後只跑了60ms,成功AC,真是很玄學。
我知道哪裏的問題了,藍書《算法競賽進階指南》上的弧優化好像有點問題,now[x] = i好像應該放到for循環裏面,我參照的洛穀日報上的寫法,跑了最後一個點只13ms。(哪位大佬能不能告訴我爲什麼)
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<math.h>
#include<cstring>
#include<queue>
//#define ls (p<<1)
//#define rs (p<<1|1)
#define over(i,s,t) for(register int i = s;i <= t;++i)
#define lver(i,t,s) for(register int i = t;i >= s;--i)
//#define int __int128
//#define lowbit(p) p&(-p)
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const ll INF = 1e18;
const int N = 5e2+7;
const int M = 2e5+7;
int head[N],nex[M],ver[M],tot = 1;
ll edge[M];
int n,m,s,t;
ll maxflow;
ll deep[N];//層級數,其實應該是level
int now[M];//當前弧優化
queue<int>q;
inline void read(int &x){
int f=0;x=0;char c=getchar();
while(c<'0'||c>'9')f|=c=='-',c=getchar();
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
x=f?-x:x;
}
inline void add(int x,int y,int z){//建正邊和反向邊
ver[++tot] = y;edge[tot] = z;nex[tot] = head[x];head[x] = tot;
ver[++tot] = x;edge[tot] = 0;nex[tot] = head[y];head[y] = tot;
}
inline bool bfs(){//在殘量網絡中構造分層圖
over(i,1,n)deep[i] = INF;
while(!q.empty())q.pop();
q.push(s);deep[s] = 0;now[s] = head[s];//一些初始化
while(!q.empty()){
int x = q.front();q.pop();
for(int i = head[x];i;i = nex[i]){
int y = ver[i];
if(edge[i] > 0 && deep[y] == INF){//沒走過且剩餘容量大於0
q.push(y);
now[y] = head[y];//先初始化,暫時都一樣
deep[y] = deep[x] + 1;
if(y == t)return 1;//找到了
}
}
}
return 0;
}
//flow是整條增廣路對最大流的貢獻,rest是當前最小剩餘容量,用rest去更新flow
ll dfs(int x,ll flow){//在當前分層圖上增廣
if(x == t)return flow;
ll ans = 0,k,i;
for(i = now[x];i && flow;i = nex[i]){
now[x] = i;//當前弧優化(避免重複遍歷從x出發的不可拓展的邊)
int y = ver[i];
if(edge[i] > 0 && (deep[y] == deep[x] + 1)){//必須是下一層並且剩餘容量大於0
k = dfs(y,min(flow,edge[i]));//取最小
if(!k)deep[y] = INF;//剪枝,去掉增廣完畢的點
edge[i] -= k;//回溯時更新
edge[i ^ 1] += k;//成對變換
ans += k;
flow -= k;
}
//if(!flow)return rest;
}
return ans;
}
void dinic(){
while(bfs())
maxflow += dfs(s,INF);
}
int main()
{
read(n);read(m);read(s);read(t);
tot = 1;//成對變換
over(i,1,m){
int x,y,z;
read(x);read(y);read(z);
add(x,y,z);
}
dinic();
printf("%lld\n",maxflow);
return 0;
}