网络流重制版:最大流Dinic,以及EK、Dinic时间复杂度的证明(含坑)
目錄前言關(guān)于最大流神奇的術(shù)語(yǔ)EK算法Dinic時(shí)間復(fù)雜度EKDinic細(xì)節(jié)與一些神奇的性質(zhì)反向弧的作用以及代碼邊中的c合法的f對(duì)應(yīng)流st有入邊,ed有出邊雙向邊的兩種處理方法s<f優(yōu)化反向邊本次無(wú)用性Dinic深度嚴(yán)格單調(diào)遞增性從起點(diǎn)跑和從終點(diǎn)跑反向邊處理方法當(dāng)前弧優(yōu)化參考資料坑
前言
初三的時(shí)候就知道以后注定會(huì)重新寫(xiě)網(wǎng)絡(luò)流的博客了。
但是呢,之前的博客是不會(huì)刪的。水?dāng)?shù)量
因?yàn)橹芭隽撕芏嚯s七雜八的東西。
萬(wàn)一刪了不就前功盡棄了,如果有少數(shù)幾個(gè)讀得懂我所寫(xiě)的文章的,可以結(jié)合兩篇一起看,遇到重復(fù)的地方以這篇為參考,加上自己的理解。
需要注意的是,這篇文章可能對(duì)于信息學(xué)新手不會(huì)太友好,如果你只是個(gè)新手,建議去看看我之前的那篇,那篇提供了一個(gè)例子的講解,會(huì)比較好,而這篇文章注重的是理論,比較干。說(shuō)實(shí)話,就是懶得寫(xiě)例子
當(dāng)然,這篇文章寫(xiě)的比較倉(cāng)促,因?yàn)檫€要備戰(zhàn)NOIP。
關(guān)于最大流
最大流是啥?
想象一坨有向的水管,每個(gè)水管連接著兩個(gè)點(diǎn),且圖中有個(gè)入水口,有個(gè)出水口,出水口可以無(wú)限出水,入水口可以瞬間收水。
但是呢,在一個(gè)單位時(shí)間,一個(gè)水管只能通過(guò)(c_{i})單位體積的水,同時(shí)水瞬間通過(guò)這個(gè)水管,并在同一個(gè)單位時(shí)間通過(guò)其余的水管,且水管不能存水,如果水不能通過(guò)這個(gè)水管一瞬間流到終點(diǎn),那么水不會(huì)流過(guò)來(lái)。有生命的水
那么,一個(gè)單位時(shí)間內(nèi)入水口最多入多少體積的水?這就是最大流問(wèn)題。
其中(a/b)分別表示流過(guò)的水和最多流過(guò)多少的水,(A)為出水口,(E)為入水口。
可以看到,上面兩個(gè)就是同一個(gè)圖的最大流,有兩種,其中,為什么第一個(gè)圖中,(AC)流過(guò)的流量是(0)呢?因?yàn)槿绻麖?A)點(diǎn)流出了一體積的水到了(C)又到了(D),無(wú)法到達(dá)終點(diǎn)(E),所以這一體積的水不會(huì)流過(guò)(C),而是選擇留在原地。
當(dāng)然,上述的表示非常的SB,因?yàn)槲掖_實(shí)不知道如何比較規(guī)范的用中文表述。
我們用((i,j))表示一條邊,(c(i,j))表示這條邊最多流過(guò)的水的體積,(f(i,j))表示這條邊流過(guò)的水的體積。
那么有以下規(guī)定。
((i,j)∈E,0≤f(i,j)≤c(i,j),(i,j)?E,c(i,j)=0)
(sumlimits_{(x,i)∈E}f(x,i)=sumlimits_{(j,x)∈E}f(j,x)(x≠st,ed))
在(E)中,(st)不存在入邊,(ed)不存在出邊。(存在就刪了)
(sumlimits_{(st,i)∈E}f(st,i)=sumlimits_{(j,ed)∈E}f(j,ed))
然后要求最大化(sumlimits_{(st,i)∈E}f(st,i))。
這個(gè)時(shí)候就有人很敏銳的意識(shí)到,那是不是圖外面有個(gè)自環(huán),這個(gè)自環(huán)也滿足要求?是的,沒(méi)錯(cuò),但是我們要求最大化(sumlimits_{(st,i)∈E}f(st,i)),這個(gè)環(huán)我們管不管無(wú)所謂。
神奇的術(shù)語(yǔ)
請(qǐng)注意:由于我的學(xué)習(xí)經(jīng)常都是不學(xué)術(shù)的,所以這些術(shù)語(yǔ)的表達(dá)甚至意思可能與真實(shí)的術(shù)語(yǔ)有一定的偏差,見(jiàn)諒。但是應(yīng)該不影響看這篇文章
弧:說(shuō)白了就是有向邊。
反向弧:如果((x,y)∈E),那么((y,x))就是((x,y))的反向弧。
我看算法導(dǎo)論的時(shí)候發(fā)現(xiàn)正規(guī)的網(wǎng)絡(luò)流定義(E)中的反向弧必須不屬于(E),但是實(shí)際上如果屬于(E)也不會(huì)影響算法,但是呢,為了后面的表述方便,我們也是默認(rèn)((y,x))不屬于(E),但是如果題目要求呢?實(shí)際上我們有類似的轉(zhuǎn)換:
可以在不影響結(jié)果的情況下保證反向弧不在(E)中。
反向弧的(f,c):((i,j)∈E,f(j,i)=-f(i,j),c(j,i)=0),(有沒(méi)有人規(guī)定非(E)元素的(f)一定非負(fù))。
網(wǎng)絡(luò):就是點(diǎn)邊形成的有向圖,其中有意義的邊只有(E)和其反向弧。
流量網(wǎng)絡(luò):網(wǎng)絡(luò)每條邊標(biāo)出其(f)。
容量網(wǎng)絡(luò):網(wǎng)絡(luò)每條邊標(biāo)出其(c)。
殘余網(wǎng)絡(luò):流量網(wǎng)絡(luò)(-)殘余網(wǎng)絡(luò)。(這里"(-)"的意思就是邊上標(biāo)號(hào)相減)
增廣路徑:從(st)到(ed)的一條路徑,且路徑上的每條邊(f<c),而這條路徑(p)的流量(f(p))就是路徑中(f-c)的最小值。
舉個(gè)例子:
當(dāng)然,這里默認(rèn)網(wǎng)絡(luò)中沒(méi)有意義的邊(比如殘余網(wǎng)絡(luò)中標(biāo)號(hào)為(0)的邊,容量網(wǎng)絡(luò)中標(biāo)號(hào)為(0)的邊)直接消失即可,畫(huà)圖方便一點(diǎn)。(updata:當(dāng)然,在后面證明的過(guò)程中,我們會(huì)發(fā)現(xiàn),這些容量流量都為(0)的邊沒(méi)有意義,不予討論,而且,在某些證明中,是只針對(duì)(f>0)的(E)的邊,在你發(fā)現(xiàn)證明看不懂的時(shí)候,或者存在很大的邏輯問(wèn)題的時(shí)候,可以考慮看看是不是考慮了沒(méi)有意義的邊)
EK算法
這個(gè)算法是根據(jù)一個(gè)依據(jù):網(wǎng)絡(luò)中不存在增廣路時(shí)就是最大流來(lái)搞的。
精髓就是每次只增廣最短的路徑(默認(rèn)邊的長(zhǎng)度都是(1)),因此只要不斷的跑分層圖,然后不斷增廣即可。
需要注意的是,一條邊的(f)改變時(shí),其反向弧也要改變。
但是我沒(méi)有代碼QMQ,因?yàn)橹苯佑肈inic的。
Dinic
我們發(fā)現(xiàn),一次建圖就跑一條增廣路,真的是浪費(fèi)!!!!
因此我們多跑幾次增廣路。
例題:https://www.luogu.com.cn/problem/P3376
代碼如下:
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long LL;
struct node
{
LL x,y,c/*還能流多少的流量*/,next,other/*反向弧的編號(hào)*/;
}a[250000];LL len,last[1300],st,ed,n,m;
void ins(LL x,LL y,LL c)
{
LL k1,k2;
len++;k1=len;
a[len].x=x;a[len].y=y;a[len].c=c;
a[len].next=last[x];last[x]=len;
len++;k2=len;
a[len].x=y;a[len].y=x;a[len].c=0;
a[len].next=last[y];last[y]=len;
a[k1].other=k2;
a[k2].other=k1;
}
LL list[1300],head,tail,h[1300];
bool bt_h()
{
memset(h,0,sizeof(h));h[st]=1;
list[1]=st;head=1;tail=2;
while(head!=tail)
{
LL x=list[head];
for(LL k=last[x];k>0;k=a[k].next)
{
LL y=a[k].y;
if(a[k].c>0 && h[y]==0)
{
h[y]=h[x]+1;
list[tail++]=y;
}
}
head++;
}
if(h[ed])return true;
else return false;
}
LL mymin(LL x,LL y){return x<y?x:y;}
LL findflow(LL x,LL f)
{
if(x==ed)return f;
LL s=0,t;
for(LL k=last[x];k>0;k=a[k].next)
{
LL y=a[k].y;
if(a[k].c>0 && h[y]==h[x]+1 && s<f/*沒(méi)有跑滿*/)
{
s+=(t=findflow(y,mymin(a[k].c,f-s)));
a[k].c-=t;a[a[k].other].c+=t;
}
}
if(s<f)h[x]=0;//如果這個(gè)點(diǎn)跑不滿,以后都不到這個(gè)點(diǎn)了
return s;
}
int main()
{
scanf("%lld%lld%lld%lld",&n,&m,&st,&ed);
for(LL i=1;i<=m;i++)
{
LL x,y,c;
scanf("%lld%lld%lld",&x,&y,&c);
ins(x,y,c);
}
LL s=0;
while(bt_h()==true)
{
s+=findflow(st,LL(999999999999));
}
printf("%lld
",s);
return 0;
}
時(shí)間復(fù)雜度
EK
事實(shí)上,對(duì)于(h[x]),不斷的增廣,(h[x])只會(huì)非嚴(yán)格單調(diào)遞增,設(shè)增廣后為(h'),增廣前在殘余網(wǎng)絡(luò)中有意義的邊構(gòu)成的集合為(E)。
什么?如何證明?
我們?cè)O(shè)一次增廣后(h[x])減少了,且(x)是所有減少的點(diǎn)中(h')最小的點(diǎn)。
設(shè)(st)到(x)的路徑為(st?y→x)。
分類討論(y->x)。
如果((y,x)∈E),(h[x]≤h[y]+1≤h[y]'+1≤h[y])。
如果((y,x)?E),說(shuō)明((x,y))是在增廣路徑中的,所以(h[x]=h[y]-1≤h[y]'-1,h[x]'=h[y]'+1≥h[x]+2)。
所以(h)單調(diào)遞增。
現(xiàn)在證明,一條增廣路徑(p),如果一條邊在路徑上且(c-f)等于(f(p)),那么這條邊被稱為關(guān)鍵邊,本次增廣完之后便會(huì)在殘余網(wǎng)絡(luò)中沒(méi)有意義。
那么其重新有意義需要圖中帶來(lái)怎樣的變化呢?其重新有意義, 必須其反向弧存在于增廣路中。
即對(duì)于弧((x,y)),反向弧存在于增廣路中:(h[x]=h[y]-1,h[x]'=h[y]'+1≥h[y]+1≥h[x]+2)。
即要求(x)的深度加(2),那么對(duì)于一條邊,其最多變成關(guān)鍵邊(frac{n-1}{2})次,所以時(shí)間復(fù)雜度就是:(O(nm^2))的。
但是需要注意的是,(x)的深度(+2)并不代表只讓一條邊有意義,比較容易陷入的誤區(qū)是:深度之和最多是(n^2)的,那么一個(gè)點(diǎn)深度(+2)不是只會(huì)讓一條邊有意義嗎?那不就是最多(frac{n^2}{2})次增廣。但是一個(gè)點(diǎn)深度(+2)不一定只讓一條邊有意義啊!!!!例子以后補(bǔ)。
當(dāng)然,這也有個(gè)推論,只要我每次找到的增廣路都保證是圖中長(zhǎng)度最小的,那么增廣路長(zhǎng)度一定非嚴(yán)格遞增。
Dinic
(Dinic)時(shí)間復(fù)雜度為什么是正確的?依據(jù)(EK)算法的推論,我們可以在一次建分層圖的時(shí)候直接把本次分層圖中所有增廣路一下子找出來(lái),這樣不就免了多次減分層圖的時(shí)間了嗎?
因此,時(shí)間復(fù)雜度還是(O(nm^2))的。(但事實(shí)上這是不是理論上界呢?不是說(shuō)(n^2m)嗎?這個(gè)會(huì)在弧優(yōu)化的時(shí)候具體分析)
細(xì)節(jié)與一些神奇的性質(zhì)
反向弧的作用以及代碼邊中的c
下文默認(rèn)是找增廣路,不管是用什么算法,反正就是找增廣路。
有人可能會(huì)問(wèn),為什么反向弧這條邊不存在于原圖當(dāng)中,為什么能夠在增廣路徑的時(shí)候走過(guò)它?
先思考反向弧在殘余網(wǎng)絡(luò)中有意義的原因?
是因?yàn)樵≡?jīng)存在于增廣路徑中。
其實(shí)反向弧的存在,就是提供了一次后悔操作。
在下圖中:
(紅邊表示正在找增廣的邊)
我們發(fā)現(xiàn),(B)堵住了,其原因是因?yàn)?(A,C))沒(méi)有走((C,D)),別跟我說(shuō)什么長(zhǎng)度最小,隨便改一下照樣卡,那么我們就設(shè)置反向邊,叫做后悔,至于其意義,后面具體講,至少我們發(fā)現(xiàn)設(shè)置反向邊后,((A,B))就可以走((B,C))直接到(D)了。
其意義現(xiàn)在講,對(duì)于路徑(p1):(st?x→y?ed),對(duì)于路徑(p2):(st?y→x?ed),那么其實(shí)質(zhì)上就是走了兩條路徑:(st?x?ed,st?y?ed)。
即:
這就是反向邊的真正含義,在對(duì)應(yīng)到(f)上面,對(duì)于((i,j)∈E),如果本次流過(guò)((j,i))流了(k)(很明顯(k≤f(i,j)),因?yàn)?f(i,j)≥0)),那么其意義上就是((i,j))之前有(k)的流量取消了(上圖中(x->y)),所以在(f(j,i)+=k,f(i,j)-=k)。
現(xiàn)在聊聊代碼實(shí)現(xiàn)中的邊的(c)代表什么。
對(duì)應(yīng)在代碼中的實(shí)現(xiàn),邊的(c)表示殘余流量(下文用(c')表示,其實(shí)就是殘余網(wǎng)絡(luò)中邊的標(biāo)號(hào)),即(c(i,j)-f(i,j)),就是(c'(i,j))(不難發(fā)現(xiàn)在代碼中(c')嚴(yán)格(≥0),滿足上面的對(duì)于(c,f)的約束),對(duì)應(yīng)一下就是(c'(j,i)-=k,c'(i,j)+=k)。
而對(duì)于((i,j))流過(guò)的流量也是同樣如此,同樣是(c'(i,j)-=k,c'(j,i)+=k)。
因此,對(duì)于代碼中的(c')的處理,是完美的符合其應(yīng)該代表的含義的。
合法的f對(duì)應(yīng)流
如果(f)合法,是不是絕對(duì)對(duì)應(yīng)著一種流呢?
發(fā)現(xiàn)圖中只有(st)的入邊(f=0),出邊(f≥0),出邊反之,定義一種網(wǎng)絡(luò)中只包含(f>0)且屬于(E)的邊,這個(gè)網(wǎng)絡(luò)中邊的容量為(f),每次拿(1)流量去從(st)跑到(ed),最終一定會(huì)找到(sumlimits_{(st,i)∈E}f(st,i))條路徑(到達(dá)一個(gè)點(diǎn)的流量和這個(gè)點(diǎn)流出的流量相等)。
當(dāng)然,圖中可能還會(huì)剩一些環(huán)。
需要注意的是,即使你用的是Dinic,也一樣可能存在環(huán),舉一個(gè)例子:
st有入邊,ed有出邊
不難發(fā)現(xiàn),(ed)的出邊(100)%不會(huì)被經(jīng)過(guò),(st)的入邊也不可能被經(jīng)過(guò)(除非增廣路走環(huán)),因此不用去提前處理使得(st)沒(méi)有入邊,(ed)沒(méi)有出邊。(除非你用的是其他算法)
雙向邊的兩種處理方法
上文也講了,其實(shí)如果原圖中就存在雙向邊有兩種處理方法:
新建一個(gè)點(diǎn),把一條邊變成鏈,即上文做法。
如果你足夠理解反向弧的話,你就會(huì)明白,其實(shí)反向邊可以直接放在一起,如果對(duì)于(c(i,j)=3,c(j,i)=5),那么你就按其說(shuō)的在圖中如此設(shè)定,這樣是完全沒(méi)有問(wèn)題的。
針對(duì)(c(i,j)=3,c(j,i)=5),我們說(shuō)明一下,幫助理解。
首先,對(duì)于這兩邊只會(huì)走一條,兩條都走可以交換執(zhí)行反向邊操作,因此,(f(i,j)≤0)或者(f(j,i)≤0)是成立的,這樣子的話,如果(f(i,j)>0)表示原圖中只走((i,j)),反之亦然。
非常SB的方法,當(dāng)兩條邊處理,各自建立反向弧,證明方法同2,100%不推薦,除非你有很大的怨念。
s<f優(yōu)化
為什么代碼中(s<f)就(h[x]=0)呢?(相當(dāng)于認(rèn)為這個(gè)點(diǎn)不能走)
難道其不能再做貢獻(xiàn)了嗎?
事實(shí)上是非常肯定的,為了更加直觀的理解,我們定義合法網(wǎng)絡(luò):
如果對(duì)于一條邊((x,y)),(h[x]=h[y]-1),那么這條邊就在合法網(wǎng)絡(luò)中。
單純?yōu)榱酥庇^理解,其實(shí)也沒(méi)必要
可以發(fā)現(xiàn),邊和反向邊一定不會(huì)同時(shí)在合法網(wǎng)絡(luò),所以,只有在合法網(wǎng)絡(luò)中的弧流量才會(huì)增加,且絕對(duì)不會(huì)減少。
因此,(x)到(ed)的路徑的集合為(P),這些路徑上的邊流量只會(huì)增加,不會(huì)減少,所以這些路徑不可能在本次分層圖(DFS)中再度成為增廣路,所以(x)以后都不用再找了。
當(dāng)然,如果你不加這個(gè)優(yōu)化,可以被分分鐘卡掉,如下圖:
反向邊本次無(wú)用性
其實(shí)從上面大家都看出來(lái)了,由于合法網(wǎng)絡(luò)中弧和反向弧不可能同時(shí)出現(xiàn),所以在這次(DFS)中,反向弧的流量在減少,但是并不會(huì)對(duì)(DFS)產(chǎn)生貢獻(xiàn),所以本次(DFS)中,你可以先不給反向弧添加流量,放外面添加。
好像并沒(méi)個(gè)卵用
Dinic深度嚴(yán)格單調(diào)遞增性
定理:Dinic中(h[ed])嚴(yán)格單調(diào)遞增。
反證法:
本次我們建完了分層圖,跑完了流量,這個(gè)時(shí)候,下一次分層圖的(h[ed])是一樣的!!!這意味著原本存在一條長(zhǎng)度為(h[ed])的增廣路徑但是我們沒(méi)跑到!!!!
什么辣雞東西???
假設(shè)最終增廣了(k)條增廣路,因此長(zhǎng)度之和為:(k*h[ed])。
其走了本次增廣的反向邊,因此不能在原有的分層圖上增廣,假設(shè)(p1)走了(p2)的反向邊,這個(gè)時(shí)候,你就會(huì)驚奇的發(fā)現(xiàn),用反向邊的真實(shí)含義,把(p1,p2)調(diào)換一下,得到了(p1',p2'),(p1',p2')的長(zhǎng)度之和為(p1,p2)的長(zhǎng)度之和(-2),然后不斷執(zhí)行反向邊的真實(shí)含義,最終導(dǎo)致(k)條增廣路徑的長(zhǎng)度和小于(k*h[ed]),那么一定存在一條路徑長(zhǎng)度小于(h[ed]),違反了(EK)的那個(gè)啥推論。
都沒(méi)有走反向邊,對(duì)于路徑(p),其一定在合法網(wǎng)絡(luò)中。
反證法:
設(shè)(d[x])為路徑(p)上(x)到(st)的距離,且(x)在路徑(p)上,如果(h[x]<d[x]),則一定存在一條增廣路徑小于(h[ed]),如果(h[x]>d[x]),怎么可能?所以一定在合法網(wǎng)絡(luò)上,所以應(yīng)該本次就增廣了。
從起點(diǎn)跑和從終點(diǎn)跑
好了,開(kāi)始講一個(gè)完全沒(méi)有多大用處的優(yōu)化:從終點(diǎn)開(kāi)始建分層圖會(huì)快一點(diǎn)。
從根本上講,這種優(yōu)化是針對(duì)于DFS找不到增廣路徑的搜索而言的,實(shí)際效果表現(xiàn)不佳很大一部分因?yàn)榇髷?shù)據(jù)下表現(xiàn)不佳以及BFS本身對(duì)于不同的搜索順序也會(huì)有一定的效率影響,在這里提出只是單純的因?yàn)檫@個(gè)優(yōu)化對(duì)于DFS確實(shí)是正優(yōu)化。(同時(shí)幫助理解網(wǎng)上說(shuō)的從(ed)建圖更加快的理論)
從(st)跑有個(gè)非常SB的事情,就是但凡從一個(gè)點(diǎn)延伸出來(lái)一條鏈,都很容易跑到這條鏈里面去。
但是就有人發(fā)現(xiàn)了,從終點(diǎn)開(kāi)始跑可以避免此情況,這不是吹的,這是有科學(xué)的依據(jù)的。
首先,增廣路一定是(ed)到(st)的一條路徑,從(st)到(ed)的深度單調(diào)遞減且固定減一。
因此,要么這條邊一定在(st)到(ed)的一條最短路徑上,就會(huì)被(DFS)(從起點(diǎn)開(kāi)始跑同樣會(huì)走這條邊),否則其的深度絕對(duì)不會(huì)是單調(diào)遞增的,因此(st)不會(huì)去訪問(wèn)他(但是從起點(diǎn)開(kāi)始跑是可能會(huì)去訪問(wèn)的),所以你會(huì)發(fā)現(xiàn),從終點(diǎn)開(kāi)始跑可以在(DFS)中減掉一些沒(méi)有必要的狀態(tài)。
反向邊處理方法
對(duì)于反向邊,代碼中采用的是(.other),但是有個(gè)更加簡(jiǎn)單粗暴的方法,一開(kāi)始設(shè)定(len=1),這樣建邊就是(2,3),(4,5)這樣的編號(hào),而這些編號(hào)亦或(1)就可以互相轉(zhuǎn)換了。
當(dāng)前弧優(yōu)化
可以發(fā)現(xiàn),一次(DFS),在一個(gè)點(diǎn)(x)在跑((x,y))的邊的時(shí)候,會(huì)出現(xiàn)兩種情況,(a[k].c<(f-s)),這個(gè)時(shí)候,(x)會(huì)給(y)等于(a[k].c)的流量,如果到達(dá)終點(diǎn)的流量不足(a[k].c),那么(y)無(wú)法訪問(wèn),這條邊作廢,如果到達(dá)了(a[k].c)的流量,這條邊滿流,作廢。
((f-s)≤a[k].c)時(shí),會(huì)給這條邊(f-s)的流量,如果跑滿了,說(shuō)明這條邊尚有余溫存在,下次還可以給,如果沒(méi)有,則(y)無(wú)法到達(dá),照樣作廢。
觀察上文,其實(shí)就是如果跑完這條邊之后,(s<f),這條邊就作廢,所以可以設(shè)置(cur)數(shù)組,直接跳過(guò)廢掉的邊,并進(jìn)行搜索(至于初始化可以在(BFS)的時(shí)候初始化,或者直接用memcpy把(last)賦給(cur))。
當(dāng)然,你可能會(huì)問(wèn),(f-s=a[k].c)時(shí),跑滿了不照樣爆廢?反正下次訪問(wèn)也可以(O(1))重置。
LL find(int x,LL f)
{
if(x==ed)return f;
LL s=0,t;
for(int k=cur[x];k;k=a[k].next)
{
int y=a[k].y;
if(h[y]==h[x]-1 && a[k].c>0)
{
s+=(t=find(y,mymin(a[k].c,f-s)));
a[k].c-=t;a[k^1/*.other*/].c+=t;
if(s==f)return f;//滿足就退出,這步也很重要
}
cur[x]=k;//這條邊沒(méi)有全部跑滿,直接溜走
}
if(s<f)h[x]=0;
return s;
}
完整代碼:
#include<cstdio>
#include<cstring>
#define N 310
#define M 11000
using namespace std;
typedef long long LL;
struct node
{
int y,next;
LL c;
}a[M];int last[N],n,m,len=1/*用異或代替.other*/,st,ed;
int cur[N];//當(dāng)前弧
inline void ins(int x,int y,LL c)
{
len++;a[len].y=y;a[len].c=c;a[len].next=last[x];last[x]=len;
len++;a[len].y=x;a[len].c=0;a[len].next=last[y];last[y]=len;
}
int h[N],list[N],head=1,tail=n;
inline bool bt_()
{
memset(h,0,sizeof(h));h[ed]=1;
head=1;tail=2;list[1]=ed;
while(head!=tail)
{
int x=list[head++];cur[x]=last[x];/*只對(duì)能走到的點(diǎn)記錄當(dāng)前弧*/
for(int k=last[x];k;k=a[k].next)
{
int y=a[k].y;
if(a[k^1].c>0 && h[y]==0)
{
list[tail++]=y;
h[y]=h[x]+1;
}
}
}
return h[st];
}
template<class T>
inline T mymin(T x,T y){return x<y?x:y;}
LL find(int x,LL f)
{
if(x==ed)return f;
LL s=0,t;
for(int k=cur[x];k;k=a[k].next)
{
int y=a[k].y;
if(h[y]==h[x]-1 && a[k].c>0)
{
s+=(t=find(y,mymin(a[k].c,f-s)));
a[k].c-=t;a[k^1/*.other*/].c+=t;
if(s==f)return f;//滿足就退出,這步也很重要
}
cur[x]=k;//這條邊沒(méi)有全部跑滿,直接溜走
}
if(s<f)h[x]=0;
return s;
}
int main()
{
LL ans=0;
scanf("%d%d%d%d",&n,&m,&st,&ed);
for(int i=1;i<=m;i++)
{
int x,y;
LL c;
scanf("%d%d%lld",&x,&y,&c);
ins(x,y,c);
}
while(bt_()==true)ans+=find(st,LL(9999999999999));
printf("%lld
",ans);
return 0;
}
當(dāng)然,也有人的當(dāng)前弧是這樣寫(xiě)的:
template<class T>
inline T mymin(T x,T y){return x<y?x:y;}
LL find(int x,LL f)
{
if(x==ed)return f;
LL s=0,t;
for(int &k=cur[x];k;k=a[k].next)
{
int y=a[k].y;
if(h[y]==h[x]-1 && a[k].c>0)
{
s+=(t=find(y,mymin(a[k].c,f-s)));
a[k].c-=t;a[k^1/*.other*/].c+=t;
if(s==f)return f;//滿足就退出,這步也很重要
}
}
if(s<f)h[x]=0;
return s;
}
我不是很喜歡這樣寫(xiě),因?yàn)檫@可能會(huì)破壞(Dinic)中(h[ed])單調(diào)遞增的性質(zhì),導(dǎo)致分層圖做的比較多(事實(shí)上確實(shí)會(huì))。
好了,重新分析一波(Dinic)的時(shí)間復(fù)雜度吧。
(EK,Dinic)慢在了找增廣的時(shí)間。
原本沒(méi)有當(dāng)前弧優(yōu)化的時(shí)候,每個(gè)點(diǎn)(x)如果一個(gè)流量都沒(méi)有,那么其不可以到達(dá),所以討論有流量的情況,有流量最壞情況下可能需要把(x)的邊全部遍歷一遍,這意味一條路徑可能還需要(O(m))去找,只不過(guò)常數(shù)較小(這也是為什么(Dinic)實(shí)際表現(xiàn)非常優(yōu)秀的原因),但是呢,加了當(dāng)前弧優(yōu)化(我的寫(xiě)法),點(diǎn)(x)每條邊要么有流量,要么被廢除,算上廢除邊的時(shí)間復(fù)雜度:(O(m)),沒(méi)經(jīng)過(guò)一條邊就會(huì)有一條增廣路,所以一條增廣路的花費(fèi)是(O(n))的,所以是(O(n^2m))。當(dāng)然,至于大眾寫(xiě)法,我不會(huì)分析QMQ。
放上一張?jiān)u測(cè)圖吧:
事實(shí)上,如果你能想到有什么方法可以使得一條邊經(jīng)過(guò)完之后絕對(duì)廢除,且不會(huì)影響(h[ed])單調(diào)遞增的性質(zhì),你就自創(chuàng)了(O(nm))的算法,當(dāng)然,這很難。現(xiàn)在雖然已經(jīng)有nm算法,但是太難懂了
參考資料
EK時(shí)間復(fù)雜度的分析
一篇寫(xiě)得不錯(cuò)得最大流博客,術(shù)語(yǔ)很齊全
論如何卡掉Dinic(我沒(méi)看懂)
咕咕討論,Zadeh Construction是個(gè)什么東西
二分圖匹配Dinic重拳出擊
各種算法的時(shí)間復(fù)雜度以及HLPP的講解
Dinic之神
最大流的正確性
各種算法的時(shí)間復(fù)雜度
算法導(dǎo)論爺Orz
坑
學(xué)習(xí)HLPP。(感覺(jué)這輩子都看不懂時(shí)間復(fù)雜度得證明)
卡掉Dinic。誤
證明二分圖中Dinic的時(shí)間復(fù)雜度。
EK時(shí)間復(fù)雜度證明中提到的例子。
總結(jié)
以上是生活随笔為你收集整理的网络流重制版:最大流Dinic,以及EK、Dinic时间复杂度的证明(含坑)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: dwcs6连接不上access数据库_d
- 下一篇: Android热修复技术原理中的代码热修