浅谈网络流的基本算法
引言
過(guò)去聽(tīng)起來(lái)高深莫測(cè)的網(wǎng)絡(luò)流算法,現(xiàn)在已飛入尋常百姓家了,對(duì)于每一個(gè)OIER,網(wǎng)絡(luò)流是一個(gè)神圣的東西(個(gè)人見(jiàn)解),但神圣的同時(shí),它并不是那樣抽象,最形象的模型就是水流,從長(zhǎng)江源點(diǎn)無(wú)限的向外流水,而大海(匯點(diǎn))則在不斷地‘喝水’,當(dāng)然,你也可以不把它想成水,或者是其他一切可以流動(dòng)的東西。而事實(shí)上,有些東西的流動(dòng)比較流暢,而某些東西可能相對(duì)而言比較粘稠,流速更慢,因此,就產(chǎn)生了一個(gè)問(wèn)題,單位時(shí)間內(nèi)的總流量最多多少,這里會(huì)根據(jù)流速給定單位時(shí)間內(nèi)的流量,這就是最先開(kāi)啟網(wǎng)絡(luò)流之門(mén)的最大流算法,它的解決方式將在后面談到,再想一下,如果水管是另一個(gè)物流公司所有,那么你會(huì)根據(jù)從哪里運(yùn)到哪里付出一定的代價(jià),?為了你自己的利潤(rùn),顯然要找一個(gè)在運(yùn)的東西最多的前提下有最小費(fèi)用的方案,這就引出了下一個(gè)問(wèn)題,最小費(fèi)用最大流。再引用某牛一句話(huà)“當(dāng)然也有有錢(qián)沒(méi)處花的傻子,去求最大費(fèi)用最大流”,而事實(shí)上,題目會(huì)出現(xiàn)這個(gè)模型,為了避免你成為傻瓜,現(xiàn)在你要給它一個(gè)新的定義:最大收益流,這時(shí)的你,變成了物流公司的經(jīng)理,而客戶(hù)的路線(xiàn)由你規(guī)劃,為了你的錢(qián)包,最大收益必不可少。
?
正文
第一部分.概念性問(wèn)題(基本定理及定義)
?? ? ? ?對(duì)于一些網(wǎng)絡(luò)流新手來(lái)說(shuō),有必要知道一些基本定義和基本定理,這些雖然看起來(lái)理論價(jià)值不大,但是現(xiàn)在的許多網(wǎng)絡(luò)流描述需要這些專(zhuān)業(yè)性的詞語(yǔ),所以還是 有些了解為好。
?? ? ??首先對(duì)于圖G
?? ????G?的流是一個(gè)實(shí)值函數(shù)?f,?f?(u,?v)?表示頂點(diǎn)?u?到頂點(diǎn)?v?的流,它可以為正, 為零,也可以為負(fù),且滿(mǎn)足下列三個(gè)性質(zhì):
1.容量限制:對(duì)所有u,?v??V?,要求?f?(u,?v)?£?c(u,?v)?。?反對(duì)稱(chēng)性:對(duì)所有u,?v??V?,要求?f?(u,?v)?=-?f?(v,?u)?。
2.流守恒性:對(duì)所有u??V?-{s,?t}?,要求???f?(u,?v)?=?0?。
3.整個(gè)流網(wǎng)絡(luò)?G?的流量?f?=???f?(s,?v)?或?f=???f?(u,?t)?。
接下來(lái)定義各種算法中都要用到的一些東東:
1.殘留網(wǎng)絡(luò)
給定一個(gè)流網(wǎng)絡(luò)G?=?(V?,?E)?和流?f,由?f?壓得的?G?的殘留網(wǎng)絡(luò)Gf=?(V?,?E?f?)?,定義?c?f?(u,?v)?為殘留網(wǎng)絡(luò)G?f??中邊?(u,?v)?的容量。如果弧?(u,?v)???E?或弧?(v,?u)???E?,則?弧?(u,?v)???E?f?,且?c?f?(u,?v)?=?c(u,?v)?-?f?(u,?v)?。
??殘留網(wǎng)絡(luò)又被稱(chēng)為剩余圖。
2.點(diǎn)的高度和層次,這是兩個(gè)相對(duì)的概念,高度的定義為到匯點(diǎn)的最短路徑長(zhǎng)度,而層次則是指到源點(diǎn)的最短路徑長(zhǎng)度(這里的路徑長(zhǎng)度按照各個(gè)邊的長(zhǎng)度都為1算),這兩個(gè)量是在最大流算法中貫穿始末的利器。
接下來(lái)引入最大流最小割定理
?對(duì)了,可能有同學(xué)還不知道什么是最小割,在這里提一下
?流網(wǎng)絡(luò)?G?=?(V?,?E)?的割?(S?,T?)?將V?劃分成?S?和T?=?V?-?S?兩部分,使得?s???S?,t??T?。定義割?(S?,T?)?的容量為?c(S?,T?),
??對(duì)?于?最?小?的?c?,?它?是?最?小?割?。
3. ?最?大?流?最?小?割?定?理
? ?在?流?網(wǎng)??絡(luò)?中,最?小?割?的?容?量?等?于?最?大?流?的?流?量?。(證?明?再?次?略?過(guò)?)
?
???第二部分.最大流的算法
下面步入與實(shí)際問(wèn)題更加接近的算法實(shí)現(xiàn)部分,首先給出問(wèn)題,給定一個(gè)流網(wǎng)絡(luò),求源到匯在單位時(shí)間內(nèi)的最大流量。
最簡(jiǎn)單而效率較好的算法 是基于增廣路的算法,這類(lèi)算法在王欣上大牛的論文中有詳細(xì)介紹,但我仍然想談?wù)勎业南敕?#xff0c;希望能起到拋磚引玉的作用?;谠鰪V路的算法主要有兩種:MPLA,Dinic,SAP.其中最簡(jiǎn)單的是MPLA,最實(shí)用最簡(jiǎn)潔也是最多人用的是Dinia,SAP的范圍也很廣,加上GAP優(yōu)化后的效率也讓人咋舌,這也是最近SAP大泛濫的原因吧!個(gè)人比較喜歡Dinic,數(shù)據(jù)變態(tài)就用最高標(biāo)號(hào)預(yù)流推進(jìn),SAP用的比較少,當(dāng)然,用什么算法還是看你自己的感覺(jué)吧。有些人認(rèn)為增廣路算法格式低效,于是想出了對(duì)于每個(gè)節(jié)點(diǎn)操作的算法,這類(lèi)算法以預(yù)留推進(jìn)為頂梁柱,MPM也勉強(qiáng)歸入這一類(lèi)吧。
?
1.MPLA算法
即最短路徑增值算法,可以有一個(gè)簡(jiǎn)單的思想,每次都找一條從源到匯的路徑來(lái)增廣,直到不能增廣為止,之中算法的正確性是可以保證的,但效率不盡如人意,有些時(shí)候,把事情格式化反而有益,這里的MPLA就是這樣,它只在層次圖中找增廣路,構(gòu)建出層次圖之后,用BFS不斷增廣,直到當(dāng)前層次圖中不再有增廣路,再重新構(gòu)建層次圖,如果匯點(diǎn)不在層次圖內(nèi),則源匯不再連通,最大流已經(jīng)求出,否則繼續(xù)執(zhí)行增廣,如此反復(fù),就可以求出最大流,在程序?qū)崿F(xiàn)時(shí)層次圖不用被構(gòu)建出來(lái),只需要BFS出各點(diǎn)的距離標(biāo)號(hào),找路徑時(shí)判斷對(duì)于f(u,v)是否有d[u]+1=d[v]即可。
如果每建一次層次圖成為一個(gè)階段,則在最短路徑增值算法中,最多有N個(gè)階段,證明再次略過(guò)。
因此在整個(gè)算法中,最多有N個(gè)階段,每個(gè)階段構(gòu)建層次圖的BFS時(shí)間復(fù)雜度為O(m),建N次,因此構(gòu)建層次圖的總時(shí)間為O(mn),而在增廣過(guò)程中,每一次增廣至少刪除一條邊,因此增廣m次,加上修改流量的時(shí)間,每一階段的增廣時(shí)間為O(m*(m+n)),共有N個(gè)階段,所以復(fù)雜度為O(n*m*(m+n))=O(nm^2),這也是該算法的時(shí)間復(fù)雜度。
?
2.Dinic算法
MPLA雖然簡(jiǎn)單,但經(jīng)常會(huì)點(diǎn)超時(shí),我們把增廣過(guò)程中的BFS改成DFS,效率會(huì)有比較大的提高么?答案是肯定的,至此我們已經(jīng)得到了Dinic的算法流程,只是將MPLA的增廣改為DFS,就能寫(xiě)出那美妙的Dinic了,同樣,分析一下時(shí)間,在DFS過(guò)程中,會(huì)有前進(jìn)和后退兩種情況,最多前進(jìn)后退N次,而增廣路最多找M次,再加上N個(gè)階段,所以Dinic的復(fù)雜度就是O(mn^2),事實(shí)上,它也確實(shí)比MPLA快很多,簡(jiǎn)潔而比較高效,這也是許多OIER選擇Dinic的理由了吧,畢竟,寫(xiě)它可能會(huì)節(jié)省出較長(zhǎng)時(shí)間來(lái)完成其他題目.
1 2 program dinic(input,output); 3 var 4 f : array[0..1000,0..1000] of longint; 5 number : array[0..1000] of longint; 6 q : array[0..10000] of longint; 7 n,m,ans,s,t : longint; 8 procedure init; 9 var 10 x,y,c : longint; 11 i : longint; 12 begin 13 readln(m,n); 14 s:=1; 15 t:=n; 16 fillchar(f,sizeof(f),0); 17 for i:=1 to m do 18 begin 19 readln(x,y,c); 20 inc(f[x,y],c); 21 end; 22 end; { init } 23 function min(aa,bb :longint ):longint; 24 begin 25 if aa<bb then 26 exit(aa); 27 exit(bb); 28 end; { min } 29 function bfs(): boolean; 30 var 31 head,tail : longint; 32 now,i : longint; 33 begin 34 fillchar(number,sizeof(number),0); 35 head:=0; 36 tail:=1; 37 q[1]:=s; 38 number[s]:=1; 39 while head<tail do 40 begin 41 inc(head); 42 now:=q[head]; 43 for i:=1 to n do 44 if f[now,i]>0 then 45 if number[i]=0 then 46 begin 47 number[i]:=number[now]+1; 48 inc(tail); 49 q[tail]:=i; 50 end; 51 end; 52 if number[t]=0 then 53 exit(false); 54 exit(true); 55 end; { bfs } 56 function dfs(now,flow :longint ):longint; 57 var 58 tmp,i : longint; 59 begin 60 if now=t then 61 exit(flow); 62 for i:=1 to n do 63 if number[i]=number[now]+1 then 64 if f[now,i]>0 then 65 begin 66 tmp:=dfs(i,min(flow,f[now,i])); 67 if tmp<>0 then 68 begin 69 inc(f[i,now],tmp); 70 dec(f[now,i],tmp); 71 exit(tmp); 72 end; 73 end; 74 exit(0); 75 end; { dfs } 76 procedure main; 77 var 78 tmp : longint; 79 begin 80 ans:=0; 81 while bfs() do 82 begin 83 tmp:=dfs(s,maxlongint>>2); 84 while tmp<>0 do 85 begin 86 inc(ans,tmp); 87 tmp:=dfs(s,maxlongint>>2); 88 end; 89 end; 90 writeln(ans); 91 end; { main } 92 begin 93 init; 94 main; 95 end.?
3.SAP算法
??? SAP也是找最短路徑來(lái)增廣的算法,有這樣一句話(huà):SAP算法更易理解,實(shí)現(xiàn)更簡(jiǎn)單,效率更高,而也有測(cè)試表明,SAP加上重要的GAP優(yōu)化后,效率僅次于最高標(biāo)號(hào)預(yù)流推進(jìn)算法,因此如果你想背一個(gè)模板,SAP是最佳選擇。SAP在增光時(shí)充分的利用了以前的信息,當(dāng)按照高度找不到增廣路時(shí),它會(huì)對(duì)節(jié)點(diǎn)重新標(biāo)號(hào),h[i]=min{h[j]}+1(c[i,j]>0),這也是SAP比較核心的思想,而根據(jù)這個(gè)我們可以發(fā)現(xiàn),當(dāng)高度出現(xiàn)間隙時(shí),一定不會(huì)存在增廣路了,算法已經(jīng)可以結(jié)束,因此,這里引入間隙優(yōu)化(GAP),即出現(xiàn)間隙時(shí)結(jié)束算法。
????在算法實(shí)現(xiàn)中,初始標(biāo)號(hào)可以全部置為0,在增廣過(guò)程中在逐漸提升高度,時(shí)間上可能會(huì)有常數(shù)的增加,但不改變漸進(jìn)時(shí)間復(fù)雜度。同時(shí)為了簡(jiǎn)潔,SAP實(shí)現(xiàn)時(shí)用遞歸,代碼不過(guò)80行左右。
1 View Code 2 program sap(input,output); 3 var 4 c : array[0..1000,0..1000] of longint; 5 h,vh : array[0..1000] of longint; 6 flow,n,m,ans : longint; 7 tmpflow : longint; 8 can : boolean; 9 procedure init; 10 var 11 i,j : longint; 12 xx,yy,cc : longint; 13 begin 14 fillchar(c,sizeof(c),0); 15 fillchar(h,sizeof(h),0); 16 ans:=0; 17 readln(m,n); 18 for i:=1 to m do 19 begin 20 readln(xx,yy,cc); 21 inc(c[xx,yy],cc); 22 end; 23 end; { init } 24 procedure dfs(now : longint ); 25 var 26 min,tmp : longint; 27 i : longint; 28 begin 29 min:=n-1; 30 tmp:=tmpflow; 31 if now=n then 32 begin 33 can:=true; 34 inc(ans,tmpflow); 35 exit; 36 end; 37 for i:=1 to n do 38 if c[now,i]>0 then 39 begin 40 if h[i]+1=h[now] then 41 begin 42 if c[now,i]<tmpflow then 43 tmpflow:=c[now,i]; 44 dfs(i); 45 if h[1]>=n then 46 exit; 47 if can then 48 break; 49 tmpflow:=tmp; 50 end; 51 if h[i]<min then 52 min:=h[i]; 53 end; 54 if not can then 55 begin 56 dec(vh[h[now]]); 57 if vh[h[now]]=0 then 58 h[1]:=n; 59 h[now]:=min+1; 60 inc(vh[h[now]]); 61 end 62 else 63 begin 64 dec(c[now,i],tmpflow); 65 inc(c[i,now],tmpflow); 66 end; 67 end; { dfs } 68 begin 69 init; 70 fillchar(vh,sizeof(vh),0); 71 vh[0]:=n; 72 while h[1]<n do 73 begin 74 tmpflow:=maxlongint>>2;; 75 can:=false; 76 dfs(1); 77 end; 78 writeln(ans); 79 end.?
4.MPM算法
????這個(gè)算法我還沒(méi)有實(shí)踐過(guò),因?yàn)樗膶?shí)現(xiàn)過(guò)程比較繁瑣,而且時(shí)間效率不高,是一個(gè)只具有理論價(jià)值的算法,這個(gè)算法每次都處理單獨(dú)節(jié)點(diǎn),記每個(gè)節(jié)點(diǎn)入流和與出流和的最小值作為thoughput(now)(定義在非源匯點(diǎn)),每次先從now向匯推大小為thoughput(now)的流量,在從點(diǎn)now向源點(diǎn)拉大小為thoughput(now)的流量,刪除該節(jié)點(diǎn),繼續(xù)執(zhí)行直到圖中只剩下源匯。時(shí)間復(fù)雜度為O(n^3),但時(shí)間常數(shù)較大,時(shí)間效率不高。
?
5.預(yù)留推進(jìn)算法
????以上的算法中,基本上都需要從大體上來(lái)把握全局,而預(yù)留推進(jìn)算法則是將每一個(gè)頂點(diǎn)看作了一個(gè)戰(zhàn)場(chǎng),分別對(duì)他們進(jìn)行處理,在處理過(guò)程中,存在某些時(shí)間不滿(mǎn)足流量收支平衡,所以對(duì)預(yù)先推出的流叫做預(yù)流,下面來(lái)看算法如何將預(yù)流變成最大流的。
????預(yù)留推進(jìn)算法有兩個(gè)主過(guò)程,push和relabel,即推進(jìn)和重標(biāo)號(hào),它是在模擬水流的過(guò)程,一開(kāi)始先讓源的出弧全部飽和,之后隨著時(shí)間的推移,不斷改變頂點(diǎn)的高度,而又規(guī)定水流僅能從高處流向低處,所以在模擬過(guò)程中,最終會(huì)有水流入?yún)R,而之前推出的多余的水則流回了源,那么我們每次處理的是什么節(jié)點(diǎn)呢?把當(dāng)前節(jié)點(diǎn)內(nèi)存有水的節(jié)點(diǎn)稱(chēng)為活躍節(jié)點(diǎn),每次對(duì)活躍節(jié)點(diǎn)執(zhí)行推流操作,直到該節(jié)點(diǎn)不再活躍,如果不能再推流而當(dāng)前節(jié)點(diǎn)仍未活躍節(jié)點(diǎn),就需要對(duì)它進(jìn)行重新標(biāo)號(hào)了,標(biāo)號(hào)后再繼續(xù)推流,如此重復(fù),直到網(wǎng)絡(luò)中不再存在活躍節(jié)點(diǎn)為止,這時(shí)源的流出量就是該網(wǎng)絡(luò)的最大流。注意,對(duì)于活躍節(jié)點(diǎn)的定義,不包括源匯,否則你會(huì)死的很慘。
????樸素的預(yù)留推進(jìn)的效率還過(guò)得去,最多進(jìn)行nm次飽和推進(jìn)和n^2m次不飽和推進(jìn),因此總的時(shí)間復(fù)雜度為O(mn^2)
????事實(shí)上,如同增廣路算法引入層次圖一樣,定下一些規(guī)則,可以讓預(yù)留推進(jìn)算法有更好的時(shí)間效率,下面介紹相對(duì)而言比較好實(shí)現(xiàn)的FIFO預(yù)留推進(jìn)算法,它用一個(gè)隊(duì)列來(lái)保存活躍節(jié)點(diǎn),每次從隊(duì)首取出一個(gè)節(jié)點(diǎn)進(jìn)行推進(jìn),對(duì)一個(gè)節(jié)點(diǎn)relabel之后把它加到隊(duì)尾,如此執(zhí)行,直到隊(duì)列為空,這樣一來(lái),預(yù)留推進(jìn)算法的時(shí)間復(fù)雜度降為O(n^3),實(shí)現(xiàn)的時(shí)候,可以加上同樣的間隙優(yōu)化,但注意,出現(xiàn)間隙時(shí)不要馬上退出,將新標(biāo)號(hào)的的高度置為n+1,繼續(xù)執(zhí)行程序,這樣會(huì)讓所有的剩水流回源,滿(mǎn)足流量收支平衡,以便最后的統(tǒng)計(jì)工作。
1 View Code 2 program preflow(input,output); 3 var 4 f,c : array[0..2000,0..2000] of longint; 5 q,h,vh,e : array[0..2000] of longint; 6 m,n,s,t : longint; 7 flow : longint; 8 procedure init; 9 var 10 i,j : longint; 11 xx,yy,cc : longint; 12 begin 13 readln(m,n); 14 fillchar(f,sizeof(f),0); 15 fillchar(c,sizeof(c),0); 16 fillchar(e,sizeof(e),0); 17 for i:=1 to m do 18 begin 19 readln(xx,yy,cc); 20 inc(c[xx,yy],cc); 21 end; 22 s:=1; 23 t:=n; 24 end; { init } 25 procedure main; 26 var 27 i,j : longint; 28 head,tail : longint; 29 now,tmp,tmph : longint; 30 begin 31 flow:=0; 32 h[s]:=n; 33 head:=0; 34 tail:=0; 35 for i:=1 to n do 36 begin 37 e[i]:=c[s,i]; 38 f[s,i]:=c[s,i]; 39 f[i,s]:=-f[s,i]; 40 if (e[i]>0)and(i<>t) then 41 begin 42 inc(tail); 43 q[tail]:=i; 44 inc(vh[h[i]]); 45 end; 46 end; 47 while head<tail do 48 begin 49 inc(head); 50 now:=q[head]; 51 for i:=1 to n do 52 if (c[now,i]>f[now,i])and(h[now]=h[i]+1)and(e[now]>0) then 53 begin 54 tmp:=c[now,i]-f[now,i]; 55 if tmp>e[now] then 56 tmp:=e[now]; 57 inc(f[now,i],tmp); 58 dec(f[i,now],tmp); 59 dec(e[now],tmp); 60 inc(e[i],tmp); 61 if (e[i]=tmp)and(i<>s)and(i<>t) then 62 begin 63 inc(tail); 64 q[tail]:=i; 65 end; 66 end; 67 if (e[now]>0)and(now<>s)and(now<>t) then 68 begin 69 tmph:=h[now]; 70 dec(vh[tmph]); 71 h[now]:=$FFFF; 72 for i:=1 to n do 73 if (c[now,i]>f[now,i])and(h[now]>h[i]+1) then 74 h[now]:=h[i]+1; 75 inc(tail); 76 q[tail]:=now; 77 inc(vh[h[now]]); 78 if vh[tmph]=0 then 79 for i:=1 to n do 80 if (h[i]>tmph)and(h[i]<n) then 81 begin 82 dec(vh[h[i]]); 83 h[i]:=n; 84 inc(vh[n]); 85 end; 86 end; 87 end; 88 flow:=0; 89 for i:=1 to n do 90 inc(flow,f[s,i]); 91 end; { main } 92 begin 93 init; 94 main; 95 writeln(flow); 96 end.?
????下面介紹最后一個(gè),也是編程難度最大,時(shí)間表現(xiàn)不同凡響的算法,最高標(biāo)號(hào)預(yù)流推進(jìn),它的思想是既然水是從高處向低處流的,那么如果從低處開(kāi)始會(huì)做許多重復(fù)工作,不如從最高點(diǎn)開(kāi)始流,留一次就解決問(wèn)題。再直觀(guān)一些,引用黑書(shū)上的話(huà)“讓少數(shù)的節(jié)點(diǎn)聚集大量的盈余,然后通過(guò)對(duì)這些節(jié)點(diǎn)的檢查把非飽和推進(jìn)變成一串連續(xù)的飽和推進(jìn)”。在程序現(xiàn)實(shí)現(xiàn)時(shí),用一個(gè)表list來(lái)儲(chǔ)存所有的活躍節(jié)點(diǎn),其中list(h)存儲(chǔ)高的為h的活躍節(jié)點(diǎn),同時(shí)記錄一個(gè)level,為最高標(biāo)號(hào),每次查找時(shí)依次從level,level-1……查找,直到找到節(jié)點(diǎn)為止,這時(shí)從表內(nèi)刪掉這個(gè)節(jié)點(diǎn),對(duì)它進(jìn)行Push,Relabel操作,直到該節(jié)點(diǎn)不再活躍,繼續(xù)進(jìn)行,直到表內(nèi)不在存在活躍節(jié)點(diǎn)。
?????它的復(fù)雜度為O(n^2*m^(1/2)),時(shí)間效率很優(yōu)秀(當(dāng)然,如果你刻意構(gòu)造卡預(yù)留推進(jìn)的數(shù)據(jù),它比MPLA還慢也是有可能的)。
1 View Code 2 program hign_node_flow(input,output); 3 var 4 c : array[0..1000,0..1000] of longint; {保存原圖} 5 f : array[0..1000,0..1000] of longint; {保存當(dāng)前的預(yù)流圖} 6 h : array[0..1000] of longint; {保存各個(gè)節(jié)點(diǎn)當(dāng)前高度} 7 vh : array[0..1000] of longint; {保存各個(gè)高度節(jié)點(diǎn)的數(shù)量} 8 e : array[0..1100] of longint; {保存各個(gè)節(jié)點(diǎn)的盈余} 9 level : longint; {當(dāng)前所有活躍節(jié)點(diǎn)的最高高度} 10 l : array[0..1000,0..1000] of longint; {保存活躍節(jié)點(diǎn)的表,l[i,0]表示高度為i的活躍節(jié)點(diǎn)數(shù),這也是不能用vh數(shù)組的原因} 11 n,m,s,t : longint; {節(jié)點(diǎn)數(shù),邊數(shù),源,匯} 12 listsum : longint; {記錄當(dāng)前在表內(nèi)的元素個(gè)數(shù)} 13 flow : longint; {記錄流量} 14 inlist : array[0..1000] of boolean; {節(jié)點(diǎn)是否在表內(nèi)} 15 q : array[0..10000] of longint; {用于BFS擴(kuò)展的隊(duì)列} 16 procedure init; 17 var 18 i,xx,yy,cc : longint; 19 begin 20 readln(m,n); 21 fillchar(f,sizeof(f),0); 22 fillchar(c,sizeof(c),0); 23 fillchar(e,sizeof(e),0); 24 fillchar(h,sizeof(h),0); 25 fillchar(vh,sizeof(vh),0); 26 for i:=1 to m do 27 begin 28 readln(xx,yy,cc); 29 inc(c[xx,yy],cc);{注意某些情況下有重邊,這樣處理比較保險(xiǎn)} 30 end; 31 s:=1; 32 t:=n; 33 end; { init } 34 procedure insect(now :longint ); {在活躍節(jié)點(diǎn)表內(nèi)插入節(jié)點(diǎn)now} 35 begin 36 inlist[now]:=true; {標(biāo)記now節(jié)點(diǎn)在表內(nèi)} 37 inc(listsum); {表中元素增加1} 38 inc(l[h[now],0]); {高度為h[now]的活躍節(jié)點(diǎn)數(shù)增加1} 39 l[h[now],l[h[now],0]]:=now; {表中高度為h[now]的第l[h[now],0]個(gè)活躍節(jié)點(diǎn)為now} 40 if h[now]>level then {更新活躍節(jié)點(diǎn)最高高度} 41 level:=h[now]; 42 end; { insect } 43 procedure bfs(); {利用BFS(反向的),求的各個(gè)節(jié)點(diǎn)的高度} 44 var 45 head,tail,i : longint; 46 begin 47 head:=0; 48 tail:=1; 49 q[1]:=t; 50 h[t]:=1; {匯點(diǎn)的高度為1} 51 while head<tail do 52 begin 53 inc(head); 54 for i:=1 to n do 55 if c[i,q[head]]>0 then {存在邊} 56 if h[i]=0 then {i節(jié)點(diǎn)高度沒(méi)有求出} 57 begin 58 h[i]:=h[q[head]]+1; {求的節(jié)點(diǎn)i的高度} 59 inc(tail); 60 q[tail]:=i; 61 end; 62 end; 63 end; { bfs } 64 procedure previous(); {預(yù)流推進(jìn)的預(yù)處理} 65 var 66 i : longint; 67 begin 68 for i:=1 to n do 69 begin 70 e[i]:=c[s,i]; {讓源點(diǎn)的出弧飽和,則弧的指向點(diǎn)的盈余要改變} 71 f[s,i]:=c[s,i]; {源點(diǎn)出弧飽和} 72 f[i,s]:=-f[s,i]; {反向弧的處理} 73 if (e[i]>0)and(i<>t)and(not inlist[i]) then {節(jié)點(diǎn)i成為活躍節(jié)點(diǎn),且不是匯點(diǎn),沒(méi)有在表內(nèi)(其實(shí)也不可能在表內(nèi))} 74 insect(i); 75 end; 76 h[1]:=n; 77 for i:=1 to n-1 do 78 inc(vh[h[i]]); 79 end; { previous } 80 function find(level :longint ):longint; {傳入當(dāng)前活躍節(jié)點(diǎn)集合的最高高度} 81 var 82 i : longint; 83 begin 84 for i:=level downto 1 do {枚舉節(jié)點(diǎn)集合} 85 if l[i,0]<>0 then {存在節(jié)點(diǎn)} 86 begin 87 find:=l[i,l[i,0]]; {返回表的尾元素} 88 inlist[l[i,l[i,0]]]:=false; {返回節(jié)點(diǎn)不再表內(nèi)} 89 dec(l[i,0]); 90 dec(listsum); {表中元素個(gè)數(shù)減一} 91 while (l[level,0]=0)and(level>0) do {更新level的值} 92 dec(level); 93 exit; 94 end; 95 exit(0); {沒(méi)有找到節(jié)點(diǎn)就返回0} 96 end; { find } 97 procedure push(now :longint ); {推流操作} 98 var 99 i : longint; 100 tmp : longint; 101 begin 102 for i:=1 to n do 103 if (c[now,i]>f[now,i])and(h[now]=h[i]+1)and(e[now]>0) then {如果當(dāng)前節(jié)點(diǎn)有盈余且有出弧不飽和} 104 begin 105 tmp:=c[now,i]-f[now,i]; {tmp記錄對(duì)弧而言能增廣的量} 106 if tmp>e[now] then {這里能增廣的量=min(tmp,盈余)} 107 tmp:=e[now]; 108 inc(f[now,i],tmp); {增廣操作} 109 dec(f[i,now],tmp); 110 inc(e[i],tmp); {修改節(jié)點(diǎn)盈余} 111 dec(e[now],tmp); 112 if (not inlist[i])and(e[i]=tmp)and(i<>t) then {接受流的節(jié)點(diǎn)一定成為活躍節(jié)點(diǎn)且不再表內(nèi),又不是匯點(diǎn)} 113 insect(i); 114 end; 115 end; { push } 116 procedure relable(now : longint ); {重新標(biāo)號(hào)} 117 var 118 i : longint; 119 tmph : longint; 120 begin 121 tmph:=h[now]; {tmph保存未重新標(biāo)號(hào)前now節(jié)點(diǎn)的高度} 122 dec(vh[tmph]); {高度為h[now]的節(jié)點(diǎn)數(shù)減一} 123 h[now]:=$ffff; {高度要取min(j)c[now,j]>0,則先賦值最大} 124 for i:=1 to n do 125 if (c[now,i]>f[now,i])and(h[now]>h[i]+1) then 126 h[now]:=h[i]+1; {更新標(biāo)號(hào)的過(guò)程} 127 inc(vh[h[now]]); {新產(chǎn)生節(jié)點(diǎn)的高度記錄進(jìn)去} 128 if vh[tmph]=0 then {GAP優(yōu)化,如果存在間隙,則最大流已求出} 129 for i:=1 to n do 130 if (h[i]>tmph)and(h[i]<n) then {讓各個(gè)節(jié)點(diǎn)均抬高到n} 131 begin 132 dec(vh[h[i]]); 133 h[i]:=n; 134 inc(vh[n]); 135 end; {不能直接退出,否則會(huì)無(wú)限執(zhí)行且不滿(mǎn)足流量平衡} 136 if (now<>s)and(now<>t) then 137 insect(now);{now經(jīng)過(guò)PUSH過(guò)程已經(jīng)不再活躍節(jié)點(diǎn)內(nèi)了,且一定有盈余,但一定要保證now不是源,匯} 138 end; { ralable } 139 procedure main; 140 var 141 tmp : longint; 142 begin 143 while listsum<>0 do {當(dāng)表中存在活躍節(jié)點(diǎn)時(shí)} 144 begin 145 tmp:=find(level); {找到最高標(biāo)號(hào)點(diǎn)} 146 push(tmp); {推進(jìn)} 147 if e[tmp]>0 then {如果推進(jìn)后該節(jié)點(diǎn)還有盈余} 148 relable(tmp); {重新標(biāo)號(hào)該節(jié)點(diǎn)} 149 end; 150 end; { main } 151 procedure print; 152 var 153 i : longint; 154 begin 155 flow:=0; 156 for i:=1 to n do {累加源的出流量} 157 inc(flow,f[s,i]); 158 writeln(flow); 159 end; { print } 160 begin 161 init; 162 bfs(); 163 previous; 164 main; 165 print; 166 end.?
?
小結(jié):
網(wǎng)絡(luò)流的最大流算法種類(lèi)繁多,時(shí)間效率編程復(fù)雜度也不盡相同,對(duì)于不同的流網(wǎng)絡(luò),選擇相應(yīng)的算法,需要在不斷實(shí)踐中摸索,這也是一個(gè)菜鳥(niǎo)到大牛的必經(jīng)之路。在一般題目中,選用Dinic是一個(gè)不錯(cuò)的想法,但當(dāng)我們發(fā)現(xiàn)網(wǎng)絡(luò)特別稠密時(shí),FIFO的預(yù)留推進(jìn)算法就要派上用場(chǎng)了,而時(shí)間比較緊但題目數(shù)據(jù)弱,我們甚至可以采用搜索找增廣路的算法。
提供最大流測(cè)試網(wǎng)址:http://hzoi.openjudge.cn/never/1003/
?
?
?????第三部分??最小費(fèi)用最大流問(wèn)題
?學(xué)習(xí)了網(wǎng)絡(luò)流的最大流算法,一定有一種十分興奮的感覺(jué),那么,就讓你借著這股興奮勁兒,來(lái)學(xué)習(xí)這一章的最小費(fèi)用流吧。
最小費(fèi)用流有兩種經(jīng)典的算法,一種是消圈算法,另一種則是最小費(fèi)用路增廣算法。
第一種,消圈算法。如果在一個(gè)流網(wǎng)絡(luò)中求出了一個(gè)最大流,但對(duì)于一條增廣路上的某兩個(gè)點(diǎn)之間有負(fù)權(quán)路,那么這個(gè)流一定不是最小費(fèi)用最大流,因?yàn)槲覀兛梢宰屢徊糠至鲝倪@條最小費(fèi)用路流過(guò)以減少費(fèi)用,所以根據(jù)這個(gè)思想,可以先求出一個(gè)最大初始流,然后不斷地通過(guò)負(fù)圈分流以減少費(fèi)用,直到流網(wǎng)絡(luò)中不存在負(fù)圈為止。
消圈算法的時(shí)間復(fù)雜度上限為O(nm^2cw),其中c是最大流量,w為非用最大值,而按特定的順序消圈的時(shí)間復(fù)雜度為O(nm^2logn)。這里的時(shí)間復(fù)雜度分析是按照用bellman-ford算法消圈得到的,用SPFA應(yīng)該可以得到更優(yōu)的實(shí)際運(yùn)行時(shí)間。
第二種,最小費(fèi)用路增廣算法。這里運(yùn)用了貪心的思想,每次就直接去找s到t的最小費(fèi)用路來(lái)增廣,這樣得到的結(jié)果一定是最小費(fèi)用,實(shí)現(xiàn)較簡(jiǎn)單,時(shí)間復(fù)雜度O(mnv),v為最大流量。用SPFA效果極好,但鑒于SPFA的不確定性,有時(shí)為了保險(xiǎn),往往運(yùn)用重新加權(quán)技術(shù),具體實(shí)踐請(qǐng)通過(guò)網(wǎng)絡(luò)或其他途徑獲得。
最小費(fèi)用流的東西并不多,事實(shí)上是使用最短路徑這種特殊的網(wǎng)絡(luò)流解決了普遍的網(wǎng)絡(luò)流問(wèn)題,只要掌握好基礎(chǔ),程序不難寫(xiě)出。
1 View Code 2 program minflow(input,output); 3 var 4 f : array[0..501,0..501] of longint; 5 c : array[0..501,0..501] of longint; 6 min,pre,d : array[0..1000] of longint; 7 q : array[0..2000] of longint; 8 v : array[0..501] of boolean; 9 m,n,s,t : longint; 10 procedure init; 11 var 12 xx,yy,cc,dd : longint; 13 i : longint; 14 begin 15 readln(n,m); 16 fillchar(f,sizeof(f),63); 17 fillchar(c,sizeof(c),0); 18 for i:=1 to n do 19 f[i,i]:=0; 20 for i:=1 to m do 21 begin 22 readln(xx,yy,cc,dd); 23 f[xx,yy]:=dd; 24 c[xx,yy]:=cc; 25 f[yy,xx]:=-dd; 26 end; 27 s:=1; 28 t:=n; 29 end; { init } 30 function argument():boolean; 31 var 32 head,tail : longint; 33 i,now : longint; 34 begin 35 for i:=1 to n do 36 d[i]:=maxlongint>>2; 37 fillchar(v,sizeof(v),false); 38 fillchar(min,sizeof(min),63); 39 head:=0; 40 tail:=1; 41 q[1]:=s; 42 v[1]:=true; 43 d[1]:=0; 44 while head<tail do 45 begin 46 inc(head); 47 v[q[head]]:=false; 48 now:=q[head]; 49 for i:=1 to n do 50 if c[now,i]<>0 then 51 begin 52 if d[now]+f[now,i]<d[i] then 53 begin 54 d[i]:=d[now]+f[now,i]; 55 pre[i]:=now; 56 if c[now,i]<min[now] then 57 min[i]:=c[now,i] 58 else 59 min[i]:=min[now]; 60 if not v[i] then 61 begin 62 inc(tail); 63 q[tail]:=i; 64 v[i]:=true; 65 end; 66 end; 67 end; 68 end; 69 if d[t]=maxlongint>>2 then 70 exit(false); 71 now:=t; 72 while now<>s do 73 begin 74 dec(c[pre[now],now],min[t]); 75 inc(c[now,pre[now]],min[t]); 76 now:=pre[now]; 77 end; 78 end; { argument } 79 procedure main; 80 var 81 ans : longint; 82 begin 83 ans:=0; 84 while argument() do 85 inc(ans,min[t]*d[t]); 86 writeln(ans); 87 end; { main } 88 begin 89 init; 90 main; 91 end.?
?
?? ? ? ? ??第四部分??網(wǎng)絡(luò)流算法的應(yīng)用
一.??最大流問(wèn)題。
一般情況下,比較裸的最大流幾乎不存在,網(wǎng)絡(luò)流這種東西考得就是你的構(gòu)圖能力,要不然大家背一背基本算法就都滿(mǎn)分了,下面介紹一道比較典型的最大流問(wèn)題。
???問(wèn)題一:最小路徑覆蓋問(wèn)題。
???題目鏈接:http://hzoi.openjudge.cn/never/1004/
???最小路徑覆蓋=|P|-最大匹配數(shù)
???而最大匹配數(shù)可以用匈牙利,也可以用最大流,而兩者在這特殊的圖中,效率是相同的,而一旦題目有一些變化,網(wǎng)絡(luò)流可以改改繼續(xù)用,而匈牙利的局限性較大。
???問(wèn)題二:奶牛航班。
?? Usaco的賽題,以飛機(jī)上的座位作為流量限制,通過(guò)實(shí)際模型的構(gòu)建,最終運(yùn)用最大流算法解決,詳解可參考國(guó)家集訓(xùn)隊(duì)論文,具體哪年的忘記了,囧。
??最大流實(shí)在難已以找到比較有意思的題目,下面進(jìn)入應(yīng)用最廣泛的最小費(fèi)用流吧!
?
二.最小費(fèi)用流問(wèn)題(最大收益流問(wèn)題)
這個(gè)問(wèn)題的模型很多下面就此解析幾道例題。
???問(wèn)題一:N方格取數(shù)
???在一個(gè)有m*n?個(gè)方格的棋盤(pán)中,每個(gè)方格中有一個(gè)正整數(shù)?,F(xiàn)要從方格中取數(shù),使任意2?個(gè)數(shù)所在方格沒(méi)有公共邊,且取出的數(shù)的總和最大。
???解析:這是一個(gè)二分圖最大點(diǎn)權(quán)獨(dú)立集問(wèn)題,就是找出圖中一些點(diǎn),使得這些點(diǎn)之間沒(méi)有邊相連,這些點(diǎn)的權(quán)值之和最大。獨(dú)立集與覆蓋集是互補(bǔ)的,求最大點(diǎn)權(quán)獨(dú)立集可以轉(zhuǎn)化為求最小點(diǎn)權(quán)覆蓋集(最小點(diǎn)權(quán)支配集)。最小點(diǎn)權(quán)覆蓋集問(wèn)題可以轉(zhuǎn)化為最小割問(wèn)題解決。
???結(jié)論:最大點(diǎn)權(quán)獨(dú)立集?=?所有點(diǎn)權(quán)?-?最小點(diǎn)權(quán)覆蓋集?=?所有點(diǎn)權(quán)?-?最小割集?=?所有點(diǎn)權(quán)?-?網(wǎng)絡(luò)最大流。
???問(wèn)題還有許多,可以參考網(wǎng)上的網(wǎng)絡(luò)流與線(xiàn)性規(guī)劃24題,里面題目比較全面(雖然好多根本用不到網(wǎng)絡(luò)流)。
最后再提一道題目,說(shuō)一下最小割的轉(zhuǎn)化建模。
The last問(wèn)題:黑手黨
題目大意:要用最少的人數(shù)來(lái)切斷從A到B的所有路徑,每個(gè)人只能切斷一條邊。
分析:顯然是一個(gè)從A到B的最小割問(wèn)題,由最大流最小割定理,求A到B?的最大流即可。
結(jié)論:網(wǎng)絡(luò)流問(wèn)題博大精深,難點(diǎn)在構(gòu)圖,這是一種能力,需要逐漸培養(yǎng)。
?
總結(jié):關(guān)于網(wǎng)絡(luò)流的介紹到這里也就結(jié)束了,但是網(wǎng)絡(luò)流絕不是僅僅這點(diǎn)東西的,由于個(gè)人水平問(wèn)題,出錯(cuò)或片面的地方還請(qǐng)大牛指正。
?
參考資料:
[1].國(guó)家集訓(xùn)隊(duì)論文2007?王欣上,淺談基于分層思想的網(wǎng)絡(luò)流算法。
[2].國(guó)家集訓(xùn)隊(duì)論文2002,江鵬,從一道題目的解法試壇網(wǎng)絡(luò)流的構(gòu)造與算法。
[3].算法藝術(shù)與信息學(xué)競(jìng)賽,劉汝佳,黃亮。
?
[轉(zhuǎn)]?http://www.cnblogs.com/neverforget/archive/2011/10/20/2210785.html
?
-----------
注:預(yù)留推進(jìn)算法Gap優(yōu)化:
若發(fā)現(xiàn)存在某個(gè)高度t<|V|,使得沒(méi)有任何點(diǎn)的高? 度為t,則可以把所有高度在( t, |V| ]之間的點(diǎn)的高度
? 全部提升到|V|+1
總結(jié)
以上是生活随笔為你收集整理的浅谈网络流的基本算法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: [转载]***编年史 之 上帝派来的**
- 下一篇: pdf文件格式下载