【網絡流】解題報告:luogu P3376 【模板】網絡最大流

題目鏈接: 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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章