前提:
私たちは、それぞれのチューブは、節水フローの最大負荷を持って、ご自宅の水供給ネットワークへの水工場は複雑な有向グラフであると想像します。水道水をオンにしないでください、あなたの家は、水なしです。しかし、水の流れを受信するためにも、必死になってあなたの家庭内の水道管網に水道場合(すべての後に、各パイプの限られた伝送容量)の上限値です。あなたはあなたが得ることができますどのくらいの水を知りたい、これは、ネットワークフロー問題があります。
長い時間を探して、インターネット上の情報を、ネットワークフローデータの多くは説明するが、しかし、理解しやすい非常に少ないです(おそらく、私は、あまりにもこんにゃく正しかったです)ちょうど人々が着信ネットワークフローを発見することを学ぶことを願ってこの記事を書きました(親指ができます)
最初の
最大流量:
何最大流量であることはミーティング点Tに到達するためにたくさんポイントを通じて、あなたは点Tに到達することができますどのくらいの水を尋ねるまで持つことができ、単純に多くのパスを介してソースポイントSからの水です。
図示と併せて理解される:、複数の点を介して複数の辺をtにsから、ストリームの各辺が(以下であることができるよりも多くはないが)、エッジ重み値を超えないようにすることができ、図の最大流量は、10 + 22 + 45であります= 77。あなたはまだ理解できない場合、我々はそれがトン個人INF都市を持って街に行きたいsを仮定して、別の言い方をするだろうが、sからは、いくつかの都市に(多くの例として図より)3枚のチケット秒の街を取得するために通過するtに最終的には市をtに達することができますどのように多くの人々尋ねるまで、他の方法ので、3左側のチケットの15トンに、10左?
:
エドモンド・カープ
パスの拡張:
パスの拡張:パスを増強する増大させることができる電流が流れる(T者に到達することができる)ように、tは秒から経路、この経路を通る流れを意味します。だから、パスを増強図が存在しない場合、最大流問題は、明らかに、常にパスを増強問題の解決に変換することができ、求めていることは最大流量に達します。具体的にどのように動作しますか?、tに広いsから直接非常に簡単見つけることができるされ、Sは広い探索から(エッジ0の右側に存在し得るようにバックエッジ重み値が減少するので)、0より大きく、エッジ重み値により外向き始め、あなたがトンを見つけ、その後、最小の右上へのパスを見つけるまで、それは(増強パスを見つけることができないまで、MI、その後、最大流量プラスマイル、パスマイナスマイル上の各エッジの右サイドを表します)T秒からパスまで。(Miがなぜあなたはより多くの人々がこのように行くと戦う必要がありますか?それを使用しますが、人々は街で止めることはできません)
コード:
書式#include <cstdioを> する#include <cstdlib> 書式#include <キュー> の#include <iostreamの> 名前空間stdを使用。 const int型INF = 2147483647; const int型MAXN = 100100; INTヘッド[MAXN]、CNT = 1、ロー[MAXN]、[MAXN]予め、N、M、S、T。 int型maxflow; BOOL V [MAXN]。 インラインint型リード(){ int型RES = 0。チャーCH = GETCHAR()。ブールBO =偽; 一方、(CH < '0' || CH> '9')BO =(CH == ' - ')、CH = GETCHAR()。 (CH> = '0' && CH <= '9')RES =(RES << 1)+(RES << 3)+(CH ^ 48)、CH = GETCHAR()一方、 BOを返しますか?-res:RES。 } 構造体ノード{int型NXT、DIS、であり; E} [MAXN << 1]。 無効アドオン(int型にint型から、 ヘッド= CNT [から]。 } EK()無効 { int型のx = Tと、 一方、(X = S!) { int型I =事前[X]。 E [i]が.dis- =低い[T]。 E [I ^ 1] .DIS + =低い[T]。 X = E [I ^ 1] .TO。 } maxflow + =低い[T]。 } キュー<整数> Q。 BOOL BFS() { (I = 1をint型、iが<= N; iが++)におけるV [I] = 0; (q.size())q.pop()一方、 V [S] = 1。 q.push(S)。 低[S] = INF。 一方、(q.size()) { int型のx = q.front()。 q.pop(); (; I I = E [i]は.nxt I =ヘッド[X] INT)のために { IF(E [I] .DIS> 0) { int型、Y = E [I] .TO。 (V [Y])が継続する場合、 低[Y] =分(低[X]、E [I] .DIS)。 プレ[Y] = I。 q.push(Y); V [Y] = 1。 (Y == T)がtrueを返す場合。 } } } falseを返します。 } ()INTメイン { N =(読み取り); M =読み取る(); S)を(読み取り。=; Tは読み取り=(); INT X、Y、C。 以下のために(; I <= M I ++はI = 1 INT) { X =リード(); Yは、読み取り=(); C =読み取ります()。 追加の(x、y、C)。(Y、X、0)を加えます。 } 一方(BFS())EK(); printf( "%d個の\ n"、maxflow)。 0を返します。 }
Dinic:
Dinicアルゴリズムは、2つのステップに分かれています。
- BFS成層(BFSにEKは、パスを増強するために探しています)
- DFSは、拡張します
(このようなものでありますようにのDFS?EKは効率的にOK、ああ、見えます?) えっ!ただ、2つのステップは言わないのですか?ノー増強パスマップまで1.2を繰り返します
それは何を意味するのでしょうか?
そして、EKは、我々はまだ増強パスがあるかどうかをBFSで数字を決定する必要がありますが、BFSでDInicアルゴリズムがわずかに異なる、今回、我々は任意のポイントのために、しかし、成層のすべてのポイントへのパスを記録しません。マルチポイントを介してそれぞれのS iから、私は、多数の層をみましょう。
実際には、唯一の最短増強パスを見つける新入生の層数を見つけるたびに、それは神話であります:
成層で、我々はS-> 1 - > 2 - > 4 - > 5 - > 3 - > T A選択しないだろう
ただ、私は次のステップが増強DFS仕上げ層を分割することである、と述べました。
Dinicでは、我々は増強パスが深い検索で見つけます:
コード:
#include<cstdio> #include<cstdlib> #include<iostream> #include<queue> #include<cstring> using namespace std; const int N = 100100; const int inf = 214748347; int cnt=1,head[10010],d[10010],n,m,s,t,maxflow; bool v[N]; struct node{int nxt,to,dis;}e[N<<1]; void add(int from,int to,int dis) { e[++cnt] = (node){head[from],to,dis}; head[from]=cnt; } queue <int> q; bool bfs() { memset(d,0,sizeof d); while(q.size())q.pop(); q.push(s); d[s]=1; while(q.size()) { int x=q.front();q.pop(); for(int i=head[x];i;i=e[i].nxt) { int y=e[i].to; if(e[i].dis && !d[y]) { q.push(y); d[y]=d[x]+1; if(e[i].to==t)return 1; } } } return 0; } int dinic(int x,int flow) { if(x==t)return flow; int rest=flow,k; for(int i=head[x];i&&rest;i=e[i].nxt) { int y=e[i].to; if(e[i].dis&&d[y]==d[x]+1) { k=dinic(y,min(rest,e[i].dis)); if(!k) d[y]=0; e[i].dis-=k; e[i^1].dis+=k; rest-=k; } } return flow-rest; } int main() { scanf("%d%d%d%d",&n,&m,&s,&t); int x,y,c; for(int i=1;i<=m;i++) { scanf("%d%d%d",&x,&y,&c); add(x,y,c);add(y,x,0); } int flow=0; while(bfs()) { while(flow=dinic(s,inf)) maxflow+=flow; } printf("%d\n",maxflow); return 0; }
还有一种超强的优化:当前弧(边)优化:
我们定义一个数组cur记录当前边(弧)(功能类比邻接表中的head数组,只是会随着dfs的进行而修改),
每次我们找过某条边(弧)时,修改cur数组,改成该边(弧)的编号,
那么下次到达该点时,会直接从cur对应的边开始(也就是说从head到cur中间的那一些边(弧)我们就不走了)。
有点抽象啊,感觉并不能加快,然而实际上确实快了很多。
代码:
bool bfs() { for(int i=1;i<=n;i++) { cur[i]=head[i];///////////////////只修改这几处,让你的代码飞快,相当于节省了dfs
d[i]=0;///////中的链式前向星,因为head【】的边有的已经在前面使用 } while(q.size())q.pop(); q.push(s); d[s]=1; while(q.size()) { int x=q.front();q.pop(); for(int i=head[x];i;i=e[i].nxt) { int y=e[i].to; if(e[i].dis && !d[y]) { q.push(y); d[y]=d[x]+1; if(e[i].to==t)return 1; } } } return 0; } int dinic(int x,int flow) { if(x==t)return flow; int rest=flow,k; for(int i=cur[x];i&&rest;i=e[i].nxt)////////////////// { cur[x]=i;///////////////// int y=e[i].to; if(e[i].dis&&d[y]==d[x]+1) { k=dinic(y,min(rest,e[i].dis)); if(!k) d[y]=0; e[i].dis-=k; e[i^1].dis+=k; rest-=k; } } return flow-rest; }
感谢__wfx 一下午的讲解,自己明白了很多
!!!!!!!!!!!!!!!!!!
缘来是你