記事のディレクトリ
最大フロー
水の流れと同じように、始点と終点の両方の写真があります。最初から最後までの最大流量が最大流量です。
上の図では、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に進みます。したがって、最終的には、正しい最大フローが得られます。
最もネガティブな面に関して、私は個人的に次の疑問を持っています
- 裏面を追加すると、無制限のdfs拡張道路がありますか?
- マイナス面を追加すると、最大流量の実際の値に影響がありますか?
それについて考えた後、私はそれを次のように説明できると感じています:
流れは始点から終点までしか流れないため、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を破裂させるかもしれません