ネットワークフロー-最大フロー(Ford-FulkersonアルゴリズムとDinicアルゴリズム)

最大フロー

水の流れと同じように、始点と終点の両方の写真があります。最初から最後までの最大流量が最大流量です。
ここに画像の説明を挿入します
上の図では、1が開始点で、4が終了点であると仮定すると、最大フローは3です。
つまり、2つのフロー1-> 3-> 4と1-> 2-> 4のフローの合計であり、各道路のフローは道路のショートボードに依存していることがわかります。 、最小の側面。

FFアルゴリズム:

Zengguang Road:

最大フローを計算するときに、道路が走っている場合(たとえば、1-> 3-> 4、この道路のフローは2)、この道路のフローは、この道路のすべての側面から差し引かれます。これは、各側の占有流量を差し引くことであり、通常、片側を減らした後の残りの容量が残留流量になるため、明らかに見やすくなっています。

その場合、拡張道路は、始点から終点までのすべての辺がゼロではない道路です。

裏:

FFアルゴリズムは、実際にはdfsごとにZengguang道路を検出し、道路のすべての側面からこの道路の流れを差し引きます。すべての道路の流れの合計が最大流れです。
しかし、ここで問題が発生します。拡張パスが間違っている場合があります。つまり、dfsの拡張パスが毎回最適であるとは限りません。
ここに画像の説明を挿入します
たとえば、上の図では、1-> 2-> 3-> 4で開始すると、拡張は行われません。エッジ3-> 4が使用されているため、エッジ1-> 3のフローは最後まで流れることができません。次に、この時点でマイナス面を確立する必要があります。つまり、3-> 2からエッジを作成します。

増強の道をたどるとき、私たちは前側の容量を引き、対応する容量を後側に足します。
次に、1-> 2-> 3-> 4を実行した後、2-> 3の容量は1であり、3-> 2(つまり反対側)の容量は2であり、次に最短パスが実行されている場合、容量は1-> 3の後、反対側の道路3-> 2に進むことができます。次に、2-> 4に進みます。したがって、最終的には、正しい最大フローが得られます。

最もネガティブな面に関して、私は個人的に次の疑問を持っています

  1. 裏面を追加すると、無制限のdfs拡張道路がありますか?
  2. マイナス面を追加すると、最大流量の実際の値に影響がありますか?

それについて考えた後、私はそれを次のように説明できると感じています:

流れは始点から終点までしか流れないため、1は不可能です。始点で接続されたエッジの容量がなくなると、フローはとにかく流出できないため、反対側のエッジを追加した後、拡張道路は無限にすることはできません。

2の場合、元々最適ではなかったので、最適な道路が特定の側で占有されていることを意味し、占有されている道路は最適な道路ではない、つまり道路を空けている必要があります。反対側は空いている道路を埋めます。実際、AはBの道路を取り、次にBにAの道路を進む機会を与えます。最終的な流れは2つの道路AとBの合計であるため、最終的な流量は変更されていません。

負の数の表現は非常に単純で、偶数を使用して正の数を表し、隣接する奇数を使用して対応する負の数を表します。正の数が負の数を見つけるか、負の数が正の数を見つける場合のみ1はXORする必要があります。

コード:
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
const int maxn = 1e5 + 5;
int n,m,cnt = 2,h[maxn];
int st,ed;
bool vis[maxn];
struct node{
    
    
    int w,e,nex;
}e[maxn * 2];
void add(int a,int b,int c){
    
    
    e[cnt].e = b;
    e[cnt].nex = h[a];
    e[cnt].w = c;
    h[a] = cnt++;
}
int dfs(int x,int flow){
    
    //参数为当前节点与当前该增光路的最大流
    vis[x] = 1;
    if(ed == x)return flow;//如果到达终点就返回
    for(int i = h[x]; i ; i = e[i].nex){
    
    
        int endd = e[i].e,w = e[i].w;
        if(!vis[endd] && w){
    
    //不被访问而且该路仍有流就可以走
            int g = dfs(endd, min(flow,w));//走一遍该路,当前流最大值取决于短板
            if(g){
    
    
                e[i].w -= g;//当前流减少g
                e[i ^ 1].w += g;//对应的反边增加g
                return g;
            }
        }
    }
    return 0;
}

int main(){
    
    
    int a,b,c;
    scanf("%d %d %d %d",&n,&m,&st,&ed);
    for(int i = 1; i <= m; i++){
    
    
        scanf("%d %d %d",&a,&b,&c);
        add(a,b,c);//正边
        add(b,a,0);//反边
    }
    int ans = 0;
    while(true){
    
    
        memset(vis,0,sizeof vis);
        int g = dfs(st,INF);
        if(!g)break;//如果流为空就退出
        else ans += g;
    }
    printf("%d",ans);
    return 0;
}
/*
4 5 1 4
1 2 4
1 3 3
2 3 2
2 4 2
3 4 6
*/

Dinicアルゴリズム:

Dinicアルゴリズムは、階層グラフを使用してFFアルゴリズムを最適化します。
ノードごとに、次のレイヤーにのみフローできるように人為的に規定されており、フローしない場合はdfsが終了します。

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
const int maxn = 1e5 + 5;
int n,m,cnt = 2,h[maxn];
int st,ed,dep[maxn];//dep用来存节点的深度
struct node{
    
    
    int w,e,nex;
}e[maxn * 2];
void add(int a,int b,int c){
    
    
    e[cnt].e = b;
    e[cnt].nex = h[a];
    e[cnt].w = c;
    h[a] = cnt++;
}
bool bfs(){
    
    //用于判断当前道路是否流通
    memset(dep,0,sizeof dep);//清空数组
    dep[st] = 1;
    queue<int> qu;
    qu.push(st);
    while(!qu.empty()){
    
    
        int t = qu.front();
        qu.pop();
        for(int i = h[t]; i ; i = e[i].nex){
    
    
            int endd = e[i].e,w = e[i].w;
            if(w && !dep[endd]){
    
    //没被标记,以及仍然有残余流量就继续走
                dep[endd] = dep[t] + 1;
                qu.push(endd);
            }
        }
    }
    if(!dep[ed])return false;//到不达就返回否
    else return true;
}
int dfs(int x,int flow){
    
    //参数为当前节点与当前流向该节点的流
    if(ed == x)return flow;//如果到达终点就返回
    int sum = 0;//表示当前的最大流
    for(int i = h[x]; i ; i = e[i].nex){
    
    
        int endd = e[i].e,w = e[i].w;
        if(dep[x] + 1 == dep[endd] && w){
    
    // 只走下一层
            int g = dfs(endd, min(flow,w));//走一遍该路,当前流最大值取决于短板
            flow -= g,sum += g;//到达x的流量减少g,当前的最大流增加g
            e[i].w -= g;//当前流减少g
            e[i ^ 1].w += g;//对应的反边增加g
        }
        if(!flow)break;//如果当前已经没有流到x的流了就退出
    }
    if(!sum)dep[x] = 0;//如果当前的最大流为0那么说明此节点之后的层不可能流向终点,那么就不要让其他节点的流再经过这个节点了
    return sum;//返回当前最大流
}

int main(){
    
    
    int a,b,c;
    scanf("%d %d %d %d",&n,&m,&st,&ed);
    for(int i = 1; i <= m; i++){
    
    
        scanf("%d %d %d",&a,&b,&c);
        add(a,b,c);//正边
        add(b,a,0);//反边
    }
    int ans = 0;
    while(bfs())ans += dfs(st,INF);
    printf("%d",ans);
    return 0;
}

ディニック+現在のアークの最適化

実際、ポイントの背後にあるレイヤーにトラフィックの寄与がない場合、このポイントは実行できないため、cur配列を追加して残りのエッジを記録するだけです。

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
const int maxn = 1e5 + 5;
int n,m,cnt = 2,h[maxn];
int st,ed,dep[maxn],cur[maxn];//dep用来存节点的深度
struct node{
    
    
    int w,e,nex;
}e[maxn * 2];
void add(int a,int b,int c){
    
    
    e[cnt].e = b;
    e[cnt].nex = h[a];
    e[cnt].w = c;
    h[a] = cnt++;
}
bool bfs(){
    
    //用于判断当前道路是否流通
    memset(dep,0,sizeof dep);//清空数组
    memcpy(cur,h,sizeof h);//每次都要复制一下h
    dep[st] = 1;
    queue<int> qu;
    qu.push(st);
    while(!qu.empty()){
    
    
        int t = qu.front();
        qu.pop();
        for(int i = h[t]; i ; i = e[i].nex){
    
    
            int endd = e[i].e,w = e[i].w;
            if(w && !dep[endd]){
    
    
                dep[endd] = dep[t] + 1;
                qu.push(endd);
            }
        }
    }
    if(!dep[ed])return false;
    else return true;
}
int dfs(int x,int flow){
    
    
    if(ed == x)return flow;
    int sum = 0;
    for(int i = cur[x]; i ; i = e[i].nex){
    
    
    	cur[x] = i;//记录剩下的边
        int endd = e[i].e,w = e[i].w;
        if(dep[x] + 1 == dep[endd] && w){
    
    
            int g = dfs(endd, min(flow,w));
            flow -= g,sum += g;
            e[i].w -= g;
            e[i ^ 1].w += g;
        }
        if(!flow)break;
    }
    if(!sum)dep[x] = 0;
    return sum;
}

int main(){
    
    
    int a,b,c;
    scanf("%d %d %d %d",&n,&m,&st,&ed);
    for(int i = 1; i <= m; i++){
    
    
        scanf("%d %d %d",&a,&b,&c);
        add(a,b,c);//正边
        add(b,a,0);//反边
    }
    int ans = 0;
    while(bfs())ans += dfs(st,INF);
    printf("%d",ans);
    return 0;
}

Luoguボードの質問
はスコープに注意を払います、ボードは大丈夫です、質問はintを破裂させるかもしれません

おすすめ

転載: blog.csdn.net/qq_36102055/article/details/107326736