1. 程式人生 > >網絡流題目詳講+題單(提高版)(持續更新中......)

網絡流題目詳講+題單(提高版)(持續更新中......)

奇數 spf 最小值 出口 說過 講解 並不會 夜間模式 所有

PS:如果你覺得自己還不夠強(和我一樣弱),可以去入門版看看

寫在前面的話(潦草)

  • 這篇博客不會講定義,理解啊什麽的,那些知識點網絡上......僅僅是題目詳講
    但是每一道題的題解和知識點還是會涵蓋的
  • 筆者還很菜,還有很多不會,只是想讓自己會了的題目大家更容易懂
  • 建議使用博客右邊的主題切換,換成夜間模式可能看起來更舒服(隨性)
  • 筆者根據自己的感受(也有一定參考性的)給題目編了個難度,有主觀色彩,可以根據實際需要來選擇

前置知識點:

網絡流題目詳講(入門版)

題目來了:

PS:若無特殊說明,均為luogu上的題目,難度有標記

網絡最大流/一般增廣路(Dinic&EK)

  • [X] ☆☆☆最小路徑覆蓋問題(網絡流24題)(其實有些地方把這個模型當板子了......)

    1. 首先需要看懂題目(沒錯,我看了很久),是讓很多條不相交的路徑去覆蓋所給的圖,問最少要多少條並輸出它們。
    2. 然後需要證明一個東西:我們一開始把每個點看做一條路徑,考慮路徑合並的問題,每兩條路徑首尾相連接起來是不是路徑數就會減一?(顯然)。而我們會要盡量減少它們的數量。
    3. 想一下怎麽可以求出點可以連起來的路徑的數量最小值,根據上面2提到的那一點,我們就可以考慮把可以合並的點數全部求出來,再拿總點數去減,是不是就是我們要求的答案了?
      那麽問題就轉化為了求哪些點可以“合並”。然後再往後看,嗯,標簽是網絡流,就考慮建圖跑吧。
    4. 有了上面的基礎應該就不難想了,把每個點分開成兩個,X[i]與源點S連容量為1的邊,Y[i]與匯點T連容量為1的邊(每個點只能在一條路徑上),然後數據讀入的邊X[i] --1--> Y[j]這個毋庸置疑了吧。最後跑Dinic求最大流就行了就行了(我建議把樣例的圖根據我講的畫出來容易理解,其實不難)
    5. 最後考慮怎麽輸出路徑,很容易想到,在Dinic的DFS過程中可以記錄每個點流向的那個點(就是會在原圖上相連的點,標記一下每一條“輸出邊”的開頭位置,暴力跳著輸出就ok)
      實在不行還是一邊看代碼吧
  • [X] ☆☆☆☆☆奶牛隱藏(最大流(費用流是錯的))

    這道題要講的話真的太累了,而且不想放圖也很難講懂,所以......(敬請諒解)
    但是相信我,同機房有個神犇Flash Hu巨佬在洛谷裏發了一篇題解,講的賊JB
    這裏再提供他的博客地址(打個廣告yeh):https://www.cnblogs.com/flashhu/

  • [ ] 最長不下降子序列問題(網絡流24題)

費用流

  • [X] ☆☆☆☆☆洞穴遇險

    1. 難度很大,我一開始並不會做,聽了zkj的講解才會做的(我是真的弱)
    2. 首先必須要想盡一切辦法把那個三角柱抽象成一條路徑(從一個端點進經過轉口(支撐部位)再從另一個端點出),抽象完之後會好理解很多......
    3. 一個不需要腦子的地方:我們的三角柱轉角肯定放在i+j為奇數的地方
    4. 開始建圖:(i+j為奇數簡稱奇數點,偶數點同理)
      首先,我很不幸地告訴你,這個題目要把點排成四列,而且......我理解了很久才懂
  • 首先奇數點和偶數點肯定是要分開的對吧(毋庸置疑),那麽我將會把奇數點和偶數點分別排成兩列(先別問為什麽,網絡流的建邊從來沒有為什麽,都是套路,對了就行)
  • 然後奇數點肯定要拆點(因為會產生貢獻,所以要對自己連邊),我們把這兩列放在四列點的中間兩列(說了抽象成路徑,奇數點是要在石頭的轉角處被經過的,路徑中間)
  • 偶數點的話 (PS:不管我bb什麽,這段話都是偶數點):
    你會發現一個性質:"石頭"路徑肯定是從一個奇數列(或者偶數列)上的偶數點連到歐數列上的另一個偶數點(奇數點),總之就是起點和終點的列數奇偶不同(yeh,簡單明了),所以把奇數列的點放在左邊(放右邊是一樣的),再把偶數列放在右邊,我們就強制好了出口和入口(左右是對稱的,沒有區別)
  • 考慮連邊:
    ①首先,上面已經說過了,中間的兩列奇數點分別自己對自己連一條流量為1,費用為點權的邊
    ②其次,對於所有的偶數點,從源點向第一列每個點連一條流量為1費用為0的邊,從第四列向匯點連一條流量為1費用為0的邊
    ③最後,相鄰的點肯定要連邊對吧,從第一列向這個點在圖上相鄰的點(一定在第二列中的)連容量為1,費用為0的邊,第三列向第四列連邊也同理

  1. 不用想了,套板子,跑一個最大費用最大流,最後拿所有點的點權之和減掉就是答案了(因為我們求得的是最大覆蓋住多少)
  2. 你問我要代碼?不行,得讓你先經歷痛苦,再去看洛谷的題解的代碼吧
    (其實就是碼的太醜了怕丟人)

預流推進算法(一種新的很吊的求最大流的方法,據說非常優秀)

網絡流題目需要註意的地方

PS:代碼尚未過編譯,現場手打的,有錯誤請指出
PS:有些淩亂,湊合著看

Part1 一定要記得

1. 建邊時的"反邊容量0"和"反邊費用負"

il void add(rg int p,rg int q,rg int o,rg int w)
{//o為容量,w為費用(變量醜)
    ljl[++cnt]=(EDGE){q,hd[p],o, w},hd[p]=cnt;
    ljl[++cnt]=(EDGE){p,hd[q],0,-w},hd[q]=cnt;
}

2. EK算法記錄前驅並處理情況的跳點情況

    int flow=Inf;
    for(int i=T;i!=S;i=ljl[pre[i]^1].to)
        flow=min(flow,ljl[pre[i]].c);
    tot+=flow,ans+=flow*dis[T];
    for(int i=T;i!=S;i=ljl[pre[i]^1].to)
        ljl[pre[i]].c-=flow,ljl[pre[i]^1].c+=flow;

3. 當前弧優化記得每次都要重新賦值cur

while(BFS())
{
    for(int i=1;i<=n;++i)cur[i]=hd[i];//<--
    while(lst res=dfs(1,n,Inf))ans+=res;

4. 跑Dinic分層時dep[S]一定要賦值為1,不然死循環

    Q.push(S),dep[S]=1;//<--

5. 增廣路時數組清零(還有數組的邊界)

其實邊界的話,我一般把S放在0號節點,T放在最後的節點,循環的時候就會順手很多

    //Dinic的BFS
    for(int i=S;i<=T;++i)dep[i]=0;//<--
    
    //費用流的SPFA
    for(int i=S;i<=T;++i)dis[i]=Inf;//<--跑最大費用則為-Inf
    while(!Q.empty())Q.pop();
    dis[S]=0,Q.push(S),in[S]=1;

6. 費用流和最大流都可以跑環

Part2 技巧

看看xzy吧,很詳細的

網絡流題目詳講+題單(提高版)(持續更新中......)