最詳細(也可能如今不是了)網絡流建模基礎

網絡流建模方法一覽

大鍋博主終於找回來了原稿。。。。ios

哈,聽說加上最詳細這幾個字會比較容易吸引人閱讀。算法

資料來源:百度百科(笑~數組

​ 還有一些dalao的博客,不過也都只是由於不想打字了就copy一些很通俗的結論過來,全文幾乎都是筆者的原創。網絡

注:此文隨着筆者的不斷作題持續更新。數據結構

已經很熟悉網絡流的dalao能夠從七開始看,或許能有些收穫。閉包

網絡流的題目,大部分都是在考建模,不多有考你板子的。。。除非你非得用EK結果被卡上天。筆者從作過的全部網絡流的題目裏尋找了一些比較經常使用和經典的建模方式,一共你們學習。函數

那麼就從簡單到難一步步看吧。工具

1、傻逼建圖法

筆者打上這兩個字是絕對沒有任何問題的,讓咱們來想一想網絡流的模板題的要求是什麼。oop

(注:筆者選取了洛谷爲題目來源,緣由竟是視覺效果友好)

很明顯, 沒有比這種題目更簡單的網絡流了,題目給你怎麼連的邊,你就照着題目連上就能夠了,幾乎是把你當作一個啥都不會的人手把手教你建圖。。可是這種題目的數據範圍通常較大,建議使用較高效率的網絡流算法。學習

2、超級源點和匯點的設置

這應該是網絡流題目中最最最最經常使用的一種方法了。常常有題目,咱們要求一些最小的利益什麼的,可是明顯沒有可用的源點和匯點或者更多狀況是題目中給出了不止一個源點和匯點,這時候就要創建超級源和超級匯,把全部的題目中的可行源點和匯點分別鏈接到超級源和超級匯上,而事實上,咱們拿到任何一道網絡流題目,除非它的源點和匯點是嚴格給定的,咱們均可以嘗試創建超級源匯從而達到減少建模難度的目的。

例題:飛行員匹配問題

題解:這個題咱們先無論輸出方案,只看要輸出的那個最大值,很明顯,若是咱們把英國飛行員當成源點,外國飛行員當成匯點的話,會有不少源點和匯點,本着方便至上的原則,創建超級源匯,而後板子便可。
前面的題目這麼簡單筆者並不想放代碼

3、最小割類問題

這實際上是一個很關鍵的定理。事實上真要細細的說這個總結裏寫的確定是不夠用的。咱們在作題的時候,有時會遇到這樣的一類問題,就是給你一個圖,而後給你邊權,問你如何在割掉的邊權儘可能小的狀況下把指定的兩個點or兩個集合分開。而後咱們就把你求得的這個最小的邊權和叫作這個圖的最小割。

那麼補上以前不想證實的結論。

首先最大流==最小割

爲何呢,想象一下,咱們如今有這麼一個有向圖,而後,最大流是小於等於割的,那麼。。。在最大流=某個割的時候這個割就是最小的。。。感性理解一下的話,一個最大流跑到那個圖上,這個圖怎麼看S也無法連到T去了。

而後最小割的惟一性?

咱們考慮一條邊是否必定在最小割中。明顯,咱們跑完一遍網絡流以後殘量網絡裏滿流的邊都是可能在最小割裏的,而後又到了跑Tarjan的時候了,若是這條邊在最小割裏,那麼跑完Tarjan以後它所鏈接的兩個端點必定是在不一樣的強連通份量裏。若是是的話,用腦子想一想,割了這條邊這個圖仍是沒分開啊。

那麼咱們也能夠由此得證,假設一條邊所鏈接的點一個在S所在的強連通份量裏,一個在T所在的強連通份量裏的話,那麼這條邊必定在最小割裏。

1.平面圖最小割=最大流=對偶圖最短路

而後就是轉換成對偶圖的最短路的問題。其實這已經不該該算在網絡流的範疇裏了(由於它變成了最短路)。那麼就不在這裏詳述了,具體的能夠去看看[BZOJ1001]狼抓兔子,雖然能夠最大流全力優化蜜汁跑過。

咱們在這裏仍是討論最小割的問題

2.割邊

這種就是咱們所說的最日常的最小割問題,咱們只須要直接求出圖的最大流就能夠了。

而後是一些高深一些的建模:有不少題目都會讓你求這樣一個東西,就是怎麼安排這些balabala的工做能夠得到balabala的利益,而後讓你去求這個最大利益。不少人都會正常的去想最大流的作法可是因爲有限制條件,因此不能像那麼求。而後咱們不妨轉換一下,若是咱們全部的均可以選,咱們是否是能夠獲得一個最大利益,而後咱們考慮的是放棄最小的利益和,這樣咱們就把這個題轉換成了一個最小割的問題,能夠利用最大流求出。

而後隨之而來的就是另外一個模型:最大權閉合圖這類題目均可以轉化爲最小割來求解,具體證實仍是請百度吧,這篇文章並非介紹這類東西的,而且筆者對這些模型的證實也不如網上的神犇。如今筆者又回來證這個東西了。請向後翻閱,天然能找到。。

例題:Luogu P3410 拍照

題解:這題就是一個最大權閉合圖的模型,求出總利益減去不可得的利益就能夠了,連邊的話比較容易就不詳述了,不過再日後的題目應該就會有連邊的思路了。畢竟前面這些都是些入門題目。

例題:方格取數問題

題解:咱們可能會有一個比較基礎的貪心思想,沒錯,就是隔一個取一個,可是這麼作並不可行,具體反例很容易找。而後咱們經過觀察,發現這道題和某最大權閉合子圖有些相似,若是咱們全取全部點,刪去最小割說不許可行。 開始考慮建圖,首先全部的奇數格子連源點,偶數格子連匯點,邊權爲點權。他們之間的邊只連奇數到偶數的,邊權爲inf,這麼連是爲了不重複計算。而後直接DINIC最大流就能夠了。

3.割點(?)

這也是最小割的一種經典問題,具體實現方式咱們等下放在拆點那個欄目裏講,這裏就先不說了。

4、拆點

說實話,網絡流最難思考的地方就在這裏了,幾乎全部拿得出來的經典的題目都是神奇的拆點而後求出最大流。

1.基礎拆點——入點和出點

常常有這一類問題,一個圖給出了點權而不是邊權,咱們在鏈接邊的時候就顯得十分很差操做,這個時候咱們每每就會有這樣一種操做,把每一個點拆成入點和出點,題目給出的連邊均由每一個點的出點連向入點,而後每一個點的入點和出點之間連一條流量爲點權的邊,就能夠知足點權的限制了。

而後讓我把剛纔的坑補一下:割點

咱們在不少時候都會發現,在求最小割的時候,咱們要割的不是邊,而是割掉一個點。這時候咱們再用剛纔的直接跑的方法很明顯就不能夠了。由於割掉一條邊最多隻影響兩個點之間的連邊,而割掉了一個點的話,與這個點相連的全部邊就都失去了做用。這個時候咱們就能夠先拆點,而後把這個點入點和出點之間的那條邊割掉,就至關因而這個點在圖中不存在了。

例題:教輔的組成

題解:本題的構圖思路能夠表示爲:
源點->練習冊->書(拆點)->答案->匯點
注意必定要拆點,由於一本書只能用一次。
因爲題目難度不大,這個題就提點到這裏了。

例題:奶牛的電信

爲何找這道奶牛題。。覺得確實是割點的題目不是很好找,剩下的題目不大適合放在這個地方。若是你好好看了剛纔的割點,這個題應該能夠直接作出來了。不詳述了。

2.按照時間拆點

有一部分題目是這樣的,咱們給出的圖的同時也給出了一個天數或者時間的限制,而後對每一天作出詢問,最後求總和,很明顯的一點是,要把每一天都連向匯點而後求出總和。這個時候咱們發現,若是其餘的點僅僅只是一個點的話,沒法知足求出每一天這個要求,由於會互相影響,這個時候咱們就相應的把這些點拆整天數這麼多個點,而後分別向向對應的天數連邊。例題的話一會會有一些筆者認爲比較好的題集中放在一塊兒,這裏就先介紹建圖方法了。

3.根據時間段拆點

這不是和上面那個同樣麼???實際上不是的,注意,剛纔那個是時間,而這個是時間段。不過說實話這種拆點方式主要是在費用流裏出現,由於有不少這種題目,就是一我的在不一樣的流量是費用是不同的,因此爲了知足這個要求,就把這我的拆成一共有多少不一樣費用的點。舉個例子:大家數學老師今天佈置做業越多,你的不滿值越大,若是是5張如下,一張卷子增長1點不滿值,若是10張如下,一張卷子增長兩點,若是10張以上,那麼一張卷子增長inf點不滿值。那麼咱們就把你拆成三個點,分別對應三種不滿值。

4.蛇皮拆點

有不少題目,拆點每每十分難想或者蛇皮至極有不少典型的例子就不一一詳述了,好比什麼一個點拆三個點四個點之類的。直接說一些例題貌似是比較好的。

[CQOI2012]交換棋子

這題不建議剛學費用流的人作,由於確實比較噁心,容易打擊到本身。首先,咱們能夠發現交換這個操做是很難去用流量描述和限制的。那咱們應該怎麼辦?若是咱們把格子上的全部黑棋子當棋子,而剩下的白子就當成空的格子,咱們就把一個交換的操做當成了一個移動的操做。那麼如何限制流量呢?咱們這個題要求的是每一個格子參與交換的次數,針對一次交換要使用兩個格子的次數,咱們把格子拆成入點,原點和出點,分別計算,至於交換次數天然讓費用承擔。好了,這只是大致的思路,可是實際上這題還有不少坑點和很差理解的地方。

拆點更多的是一種工具,並且是網絡流裏必要和強大的工具。能不能想出拆點的方法從而跑出網絡流,每每是解決網絡流問題的關鍵。

5、枚舉和二分

原先這個版塊叫枚舉而二分最大流量,如今想一想,當時的理解仍是不夠深入,實際上,可以枚舉和二分的遠遠不止流量。這裏詳細從新講述一下。

1.枚舉和二分流量條件

固然,這個部分依然是必不可少的。

有這樣一部分題目,它給你的圖不必定是完整的,每每須要你肯定一個值來肯定是否可以跑出指望的最大流。就好比說去計算一個最少的時間或者花費時,咱們並無一個具體的數值,這個時候咱們每每預先經過鏈接inf先跑一遍最大流,以後二分時間每次重構圖,再次跑最大流,而且經過此時的流量是否和剛纔相等來調整二分的上下界。抑或跟人數有關的題目,咱們二分的時候判斷最大流是否等於當前人數。

而後枚舉的建圖方法就更是簡單,每次枚舉+1時重構圖,而後一直跑到不能再跑了爲止。

2.枚舉和二分費用

費用流的題目中,有些題目會給你費用的限制,或者說是間接的控制了你的流量,好比說他要一個知足條件時可能的最小費用,你仍然能夠像剛纔二分流量時那麼跑。可是它也有可能要一個不超過一個費用時能知足的最大條件,這個時候去二分那個條件,而後控制費用。

3.枚舉和二分邊和點

有的題目裏,各個點的添加是有順序的,每一個點的邊的添加也多是有順序的,這個時候就有一類問題會讓你肯定一個值,要這個值以前的全部點能夠知足最大流量,這個時候咱們就要二分加入圖中的點了,邊也是同理。不過總的來講,二分的方法萬變不離其宗,老是要經過流量或者費用調整二分上下界。

6、點的構造

大部分題目是有跡可循的,由於他們至少有點或者有明顯可以當作點的狀態。或者題目給出的點就真的可以當成點使用。可是也有的題目,並無明顯的點的提示,或者給出的你明顯的點徹底不可以當成網絡流裏的點使用。對於這種題目,咱們就要本身構造出來一些新的點來進行網絡流的使用。
單說可能不是很好理解,咱們放上幾道題給你們看一下。

[HEOI2016/TJOI2016]遊戲

這道題目大意就是讓咱們在一個矩陣裏放東西,若是沒有硬石頭限制,就是同行同列只能放一個。而後考慮算法,n,m<=50很明顯這題與什麼數據結構啊什麼的是沒有緣分了。那麼就是DP或者網絡流了。。考慮DP明顯的狀態太多根本無法轉移,那麼網絡流呢?這一個一個點徹底沒有什麼用處連起來也無法限制。。。這時候咱們就要對這些已經有的點進行重構,根據網絡流可以描述互斥關係的原理來搞出這道題。
那麼:
這張網格圖能夠抽象成一系列行塊和列塊,列塊和行塊就是說,這一個橫行塊或縱列塊裏至多放一個炸彈,另外顯而易見的,咱們發現選中一個點的時候會同時選中兩個塊,那麼也就是說這張圖知足兩個限制條件
1.每一個點最多被選中一次
2.某些點對之間有必選關係
那麼以後連邊跑網絡流便可了。

BZOJ4950:[Wf2017]Mission Improbable

先說一下題目大意,就是有一個網格方陣,每一個格子裏都有一個權值,題目要求你在保證每行每列最大的權值不變的狀況下,取走儘可能多的權值。而且原本有權的格子裏不能取成0。

不取成0不用考慮,留下1就能夠了。那麼剩下的就是保證每行每列最大值不變了。咱們能夠貪心的考慮每行都把那個最大的留下,若是有那麼一行一列的箱子的最大值是相同的,這個時候把最大值放在交點處很明顯就要比放在其它的地方要好的多。那麼怎麼處理這種狀況呢?能夠像上面的題目那樣把每行每列都構形成點,而後一旦出現了剛纔那種點,就連起來。以後跑一遍最大流,就能夠求出來咱們可以放在交點出的最大值是多少了。

7、動態思想

1.基本介紹

有一些題目是這個樣子的,他們每每有着較其餘網絡流題目更大的數據,而且他們也有一個特色,就是在一部分邊連好以後,以後是先建建圖或者是跑一個點再加一個點這麼建圖對最終結果並無影響。這樣一來咱們就能夠經過動態加點或者邊的方式使其知足題目要求的複雜度。

2.限制條件

2.1數據範圍不能過大

雖然說是減少了時間複雜度,可是也只是減小了部分,不少狀況下複雜度的等級並無變化,只是由於在大部分操做裏減小了一些沒必要要的操做使速度加快。

2.2主要應用於費用流

大多時候動態加點的題目都是費用流題目,緣由就是最短路一次基本上也就只能增廣出來一條增廣路,若是是網絡流的話,當前弧優化每每可以取到更好的效果。費用流因爲大部分人都會使用EK的樸素算法,因此動態加點能夠是速度提升好幾個檔次。

2.3當前最優原則

剛纔說過了前面點的選擇不會影響後面的選擇,這裏仔細說一下。由於在跑網絡流的時候,不可避免的後面跑的點回合前面跑的點有衝突,這個時候咱們能夠經過反邊來使衝突化解。那麼若是咱們一個一個的加點,很明顯就沒辦法知足這個條件。可是若是咱們能夠肯定當前這個點跑了這個流,後面的點跑這個流的必定不如這個點優,那麼咱們就能夠無視那個衝突了。

3.經典的例子

對於這個思想有一個很經典的題目,更好的是它甚至有數據弱化版來對比。

[NOI2012]美食節&&[SCOI2007]修車

這兩個題的原理都是同樣的,就是差在了一個數據範圍,咱們先說後面那個數據範圍小的。

題目描述

同一時刻有N位車主帶着他們的愛車來到了汽車維修中心。維修中心共有M位技術人員,不一樣的技術人員對不一樣的車進行維修所用的時間是不一樣的。如今須要安排這M位技術人員所維修的車及順序,使得顧客平均等待的時間最小。

說明:顧客的等待時間是指從他把車送至維修中心到維修完畢所用的時間。

數據範圍

(2<=M<=9,1<=N<=60), (1<=T<=1000)

Solution

要求平均時間最短,就等同於要求總時間最短

對於一個修車工前後用\(W_1-W_n\)的幾我的,花費的總時間是

\[W_n\times 1+W_{n-1} \times 2+...+W_1 \times n\]

不難發現倒數第a個修就對總時間產生a*原時間的貢獻

而後咱們將每一個工人劃分紅N個階段,(i,t)表示修車工i在倒數第t個修

能夠建一個二分圖,左邊表示要修理的東西,右邊表示工人+階段

因而能夠從左邊的e向右邊的(i,t)連邊,權值是\(Time[e][i]*t\),就是第e個用i這個修車工所用時間

最小權值徹底匹配後,最小權值和除以N就是答案

由於權值是正的,因此一個修車工接到的連線必定是從(i,1)開始連續的,也符合現實狀況

由於假設是斷續的,那後面的(i,n)改連向(i,n-k),k<n時,答案更優,違背了前面的最優性

這樣咱們創建好圖之後直接跑費用流就能夠了。

那麼咱們繼續看下一道題

題目描述 2.0

m個廚師都會製做所有的n種菜品,但對於同一菜品,不一樣廚師的製做時間未必相同。他將菜品用1, 2, ..., n依次編號,廚師用1, 2, ..., m依次編號,將第j個廚師製做第i種菜品的時間記爲 ti,j 。小M認爲:每一個同窗的等待時間爲全部廚師開始作菜起,到本身那份菜品完成爲止的時間總長度。換句話說,若是一個同窗點的菜是某個廚師作的第k道菜,則他的等待時間就是這個廚師製做前k道菜的時間之和。而總等待時間爲全部同窗的等待時間之和。如今,小M找到了全部同窗的點菜信息: 有 pi 個同窗點了第i種菜品(i=1, 2, ..., n)。他想知道的是最小的總等待時間是多少。

數據範圍 2.0

對於100%的數據,n <= 40, m <= 100, p <= 800, ti,j <= 1000 (其中p = ∑pi)

Solution 2.0

連邊仍是和上一個題目同樣。那麼數據範圍變大了,咱們如今的時間複雜度就是O(\(n^2m\times p \times \overline k\))嗯,T了。

那麼怎麼辦呢?經過仔細的思考能夠發現,咱們觀察發現,第一次spfa得出的最短路確定是某人倒數第一個修某車某廚師倒數第一個作某菜,由於倒數第一個確定比倒數第二個距離短

那麼咱們能夠在一開始建圖的時候,只把全部「倒數第一個作的菜」的那些邊加上

一旦一條增廣路被用掉了(也就是一個廚師-作菜順序二元組(j,k) 被用掉了),那麼咱們就把全部表明二元組(j,k+1) 加上去(一共有n條),再跑spfa

這樣咱們圖中的總邊數不會超過$n\ast\sum_{i=1}^n p \lbrack i\rbrack $

也就是總時間在\(O\left(np^2\ast \overline k\right)\)左右,k是spfa常數,就能夠經過這道題。

8、二分圖

二分圖又稱做二部圖,是圖論中的一種特殊模型。 設G=(V,E)是一個無向圖,若是頂點V可分割爲兩個互不相交的子集(A,B),而且圖中的每條邊(i,j)所關聯的兩個頂點i和j分別屬於這兩個不一樣的頂點集(i in A,j in B),則稱圖G爲一個二分圖。

好了不介紹這東西的基本定義了隨便百度百度就應該能百度到了,至於增廣路什麼的也都去看看基礎教程吧,筆者這裏說的是網絡流的建模。。

1.最大匹配

衆所周知(?),二分圖的最大匹配就是在二分圖上跑出來的最大流。那麼最優匹配什麼的也能夠網絡流搞搞搞出來,並且時間複雜度通常都要比想象的好不少,大部分比匈牙利要快。。。

2.基本定義

爲了方便待會理解,這裏特別放上幾個基本的定義。

最小覆蓋:即在全部頂點中選擇最少的頂點來覆蓋全部的邊。

最大匹配:二分圖左右兩個點集中,選擇有邊相連的兩個匹配成一對(每一個點只能匹配一次),所能達到的最大匹配數。

獨立集:集合中的任何兩個點都不直接相連。

3.最小覆蓋數=最大匹配數

這個比較基礎,證起來主要看本身理解吧,具體的證實我也沒有特別好的思路,不過仍是儘可能給你們證一下吧。咱們先搞一個二分圖,而後最大匹配一下,如圖,綠點就是匹配成功的點,紅點就是匹配失敗的點。那麼咱們能夠發現,每一條邊都有兩種狀況,一種是連了一個紅點一個綠點,一種是連了兩個綠點。能夠發現,沒有任何一條邊會鏈接兩個紅點,因此咱們在選擇覆蓋點的時候都選綠點。

img

而後又由於若是一個點鏈接了一個紅點,那麼與這個點相連的其餘的綠點都必定不會鏈接紅點,本身能夠畫一畫,明顯能夠發現若是鏈接紅點的話,那麼剛纔所求的就不是最大匹配了。那麼對於這些點,咱們確定選擇那個鏈接了紅點的點做爲覆蓋點,由於並不會有其餘的點與那個紅點相連了。因此能夠基本上的出最小覆蓋數=最大匹配。

那麼咱們就把這類問題轉化成了網絡流了。

4. 最大獨立集=總點數-最小覆蓋集

這個定理很是好用,很多題目都直接間接的用到了這個東西。先上圖:

img

是否是很熟悉,沒錯就是剛纔那個圖,筆者實在懶得畫另外一個了。咱們用那兩個綠點完成了對這個圖的最小覆蓋。

而後咱們來反證這個結論,即證最小覆蓋集+最大獨立集\(\neq\)總點數。

首先咱們能夠看出除了咱們選擇的兩個用來最小覆蓋的點以外,剩下的點之間彼此之間都沒有連邊,咱們能夠嘗試把任意兩個紅點之間連一條邊,那麼明顯,咱們不知足最小覆蓋的要求了,或者咱們嘗試經過轉換使最小覆蓋更小。。固然不可行,由於咱們已經求得就是最小覆蓋了,而且易證的是剩下的全部點必定構成一個獨立集。而且這個獨立集的大小不可以更大了,而後咱們就證出了題目所給的定理。

5.刪邊

題目不適合起太長,這一部分主要是說:在一個二分圖中,若是刪去一條邊可以使這個圖的最大匹配減少1的話,那麼這條邊必定在殘量網絡中滿流,而且它所鏈接的兩個點必定不在同一個強連通份量當中。

首先滿流的條件必定要有,不滿流那這條邊就不在最大匹配裏了。而後就是不在同一個強連通份量裏這個條件,咱們能夠發現,在同一個強連通份量裏的話,那他們就能夠本身在這個強連通份量的內部進行增廣,也就是說咱們能夠經過操做從而找到另外一組最大匹配而且不須要使用當前這條邊,因此這條邊刪掉也就無可厚非了。

6.最小路徑覆蓋

說一下大意,就是如今有一個有向無環圖G,要求用盡可能少的不相交的簡單路徑覆蓋全部的節點 。

那麼這個也有一個結論,就是最小路徑覆蓋=原圖節點數-最大匹配

誒,等等,哪裏來的最大匹配。。

咱們對當前的圖拆點,把每一個節點都拆成x,y兩個節點。若是\(x_1有一條指向x_2的邊,那麼就把x_1和y_2之間連一條邊\),這樣咱們就能獲得一個二分圖了,那麼爲何這麼作可行呢?

一開始每一個點都是獨立的爲一條路徑,總共有n條不相交路徑。咱們每次在二分圖裏找一條匹配邊就至關於把兩條路徑合成了一條路徑,也就至關於路徑數減小了1。因此找到了幾條匹配邊,路徑數就減小了多少。因此有最小路徑覆蓋=原圖的結點數-新圖的最大匹配數。

以上就是咱們經常使用的網絡流構圖的基本思想了,接下來筆者會從作過的題目裏挑選出來一些以爲建模比較有表明性的題目分享給你們

9、經典的建模

其實你也能夠看出來筆者這個地方寫的很青澀,那是早以前寫的了,可能對於剛剛入門網絡流的人比較有用,可是對於大佬們確定用處不大了,下面的題目確定也都有本身獨特的看法和理解方式,因此請dalao從6開始看。。。

1.方格取數增強版

注意,這道題和剛纔那個不是一道題目。

記不記得咱們NOIP曾經考過一道叫作方格取數題目。這道題就是那個題的增強版。原本是要走2次,如今變成了要走n次。。原來的DP方案好像一會兒就垮掉了。如今咱們來這麼考慮,咱們把這個題當成一個圖論題而不是DP題,那麼咱們走過的格子就要拿走相應格子的點權,剛纔說過什麼,若是是點權問題的話,是否是要拆點。那麼咱們又怎麼處理每一個格子能夠走無數次呢?好辦,咱們對於每一個格子的入點和出點分別連兩條邊,一條是費用爲點權流量爲1的邊,表示能夠取走這個格子裏的點權;一條是費用爲0流量爲inf的邊,表示這個格子能夠無數次通過。而後相應的創建超級源和超級匯,分別向左上角和右下角鏈接流量爲k的邊來限制要走k次這個條件。這樣咱們是否是就作出了這個題。

2.[CQOI2015]網絡吞吐量

這道題就開始慢慢的使建圖複雜起來了,咱們根據題意能夠發現這道題的網絡流是嚴格的按照最短路跑的。等等?按照最短路跑?這和樹上跑網絡流有個P區別,不直接寫出來那個最小的流量O(1)出解更好麼?而後筆者就畫了畫樣例模擬了一下,發現這個樣例,並不僅有一條最短路!!!因此咱們先求出來一遍最短路以後,經過遍歷一個點的全部出邊的方式斷定到下一個點的是否是最短路。若是$ dis[v]=dis[u]+edge[i].dis $那麼咱們就找到了到下一個點的一條最短路。而後連邊求最短路便可。

3.數字梯形問題

這個題有三問,雖然很類似就是了。讓咱們一問一問解決這個題目。P.s.把這個題放在這裏的意義就是讓你們思考一下網絡流裏鏈接inf邊的道理。

首先先從起點到最上層的每一個數字連流量爲1的邊,表示從這些數字開始走,而後從最下層的每一個數字連到匯點,流量爲1。

①從梯形的頂至底的 m 條路徑互不相交;

這一問就是每一個點和每條邊都只能用一次的意思,因此練出來流量爲1的邊跑費用流就能夠了。

②從梯形的頂至底的 m 條路徑僅在數字結點處相交;

那麼咱們如今就能夠使用重複的點了對不對。。。因此入點和出點的連邊咱們就能夠連inf了,表示這個點能夠用無數次。而後咱們再跑一遍費用流。

③從梯形的頂至底的 m 條路徑容許在數字結點相交或邊相交。

話說。。邊怎麼相交,這個題貌似相交不了。。實際上就是能夠通過重複的邊,而後咱們每一個點的出點連向下個點的入點的流量也設置爲inf而後咱們再跑費用流。

好了,看上去這道題A了,那麼交上去試試?而後你就會有這種效果:

爲何,就是由於這一句話 「而後從最下層的每一個數字連到匯點,流量爲1。」,若是咱們這麼連邊了,那麼最後一層的點,就只能使用一次,而後你不就掛掉了。。。因此從第二次開始,咱們就要把這條邊連inf變成1,而後就能夠了。

4. 餐巾計劃問題

網絡流24題裏仍是有很好的題目的,好比說這一道,建圖方式妥妥的一股清流。首先咱們發現這個題的狀態不少,首先是兩個洗毛巾的操做,這個比較好辦,拆點了以後直接去洗就能夠了,而後就是這個題目的精髓所在,對於這種直接購買進來的操做,咱們往常的思路是向入點連邊,而後限制流量,然而這個題咱們僅僅只在入點限制流量,而把買餐巾這個操做連到當天的出點上,這樣就能夠使在洗掉的餐巾不足以支持滿流的狀況下是這一天滿流。而後由於咱們有攢着餐巾不使用這種操做,因此咱們要把上一天的入點和下一天的入點之間連一條inf的邊表示這種狀況。

5.[CTSC1999]家園

這個題具體來講就是一個拆門的操做(霧),先讓筆者來講明一下拆門這個操做是什麼含義吧。。

筆者有次作了一道叫作緊急疏散的網絡流題目,一開始筆者是用時間點建分層圖,結果發現不能夠這麼作,整個題目會爆炸掉,可是這個題又沒有更好的方法去支持時間的限制,而後筆者發現能夠有拆門這種操做,由於門,也就是指定的匯點的個數老是有限的,哪怕你把這東西拆成500個點,也不會形成有太多邊的狀況。而後從天天向下一天連邊,就能夠在一些題目上避免使用分層圖形成複雜度太高。

那麼這個題咱們也能夠這麼幹。首先咱們把天天的地球和月球都和超級源點和匯點連inf的邊,而後把太空船上一天的位置和這一天的位置連一條容量爲太空船裝載人數的邊,而後再從每一天的向下一天連一條inf邊表示能夠用人留在這裏,咱們就能夠經過每一天的最大流跑出結果。而後是一個操做,大部分這種題目咱們要二分答案,然而二分的代價就是你要有很是繁雜的預處理過程才能保證你建出來的圖沒有問題,然而這個題的數據範圍小到讓你能夠直接枚舉,而且每次不把圖清零,而是在已有的殘量圖上繼續跑,從而達到不錯的時間效率與較低的代碼難度。


從這裏開始這一個專題要變得不和諧起來了,而且可能有我本身YY的神奇的模型。

6.黑白染色

怎麼說呢,黑白染色這東西,很迷。

6.1適用性

黑白染色必定是在棋盤圖上,嗯,這點絕對沒有問題。而後就是要把棋盤的格子當作點,而且通過了染色以後,你會驚奇的發現黑點只會限制白點,不會限制黑點,白點也只會限制黑點。也就是說同色的點之間並無直接關係。

6.2爲何使用它

當你拿到一個棋盤的題手足無措,就要被水淹沒時,黑白染色頗有可能救你一命。黑白染色,一共只有兩種顏色,因此這樣建圖以後通常都會和二分圖很是像,而後就是隨便匹配的事了。另外一種狀況是你把這題轉化成了一個最小割的模型,發現題目中刪去某個點或者添加某個點的操做就是在刪邊。從而隨便網絡流跑出來這個題。

6.3模型創建

基本上能夠肯定的是你選擇一個你喜歡的顏色,把它連向你不喜歡的那個顏色,而後你不喜歡的那個顏色去連匯點,你喜歡的連源點。可是問題也就來了,這個東西適用於幾乎全部要用到黑白染色的題目,那麼也就是說,它沒屁用。實際上,真正噁心的通常都在黑白點互相鏈接的限制上,因此具體問題只能具體分析。

6.4Difficult

然而有的時候發現黑白染色並不能徹底解決問題,或者說這個題並不能符合黑白染色的定義。。那麼咱們就紅黃藍染色,再不行就紅黃綠藍染色。而後從新構造模型。不得不說,這東西有的時候仍是能派上大用的。

6.5時間複雜度

黑白染色的題目,咱們必定要相信信仰,由於建出來的圖基本上都是那種二分圖,仍是邊比較稀疏的那種,因此咱們能夠幾乎在\(O(n^{1.5})\)的時間複雜度就解決問題,因此不要問爲何有的題1e5的數據都跑網絡流了。

7.切糕模型

事先證實,這個鬼畜的名字不是我叫出來的,是網上某個博主這麼叫的,而後我一想確實是頗有道理。這種模型原本應該叫距離限制模型。也就是說,每一個元素選擇時,有多種選擇,而且相鄰兩個元素之間的選擇會相互限制。例題就是HNOI某一年的一道叫切糕的題目。

其實這個模型挺經典的,可是不少狀況下題目不只僅只是有這樣一個限制,而後就被其它的模型覆蓋了。

8.最大密度子圖

這個事實上均可以單獨拿來當成一個題作了。先說一下要求:

給定一個無向圖,要求從無向圖裏抽出一個子圖,使得子圖中的邊數\(\mid E\mid\)與點數\(\mid V\mid\)的比值最大,即求最大的\(\frac{\mid E\mid}{\mid V\mid}\).

給出一種解法,由前面說過的最大權閉合子圖獲得的。假設答案爲k ,則要求解的問題是:選出一個合適的點集 V 和邊集 E,令(|E|−k∗|V|)取得最大值。所謂「合適」是指知足以下限制:若選擇某條邊,則必選擇其兩端點。

建圖:以原圖的邊做爲左側頂點,權值爲1;原圖的點做爲右側頂點,權值爲 −k (至關於 支出 \(k\))。  若原圖中存在邊 (u,v),則新圖中添加兩條邊 ([uv]−>u), ([uv]−>v),轉換爲最大權閉合子圖。 其中k能夠二分獲得。


鍋,忽然發現這個東西沒證。。。。

而後發現第一步並不能證出來。。。

如今我又證出來了。。。

無向圖的子圖的密度:\(D=\frac{|E|}{|V|}\)

當作是分數規劃問題,二分答案 λ ,須要求

\(g(\lambda)=max(|E|-\lambda|V|)\)

g(λ)=0時咱們取到最優解。

喏,如今權當我這一步證出來了。

給出證實吧。

給出函數\(\lambda=f(x)=\frac{a(x)}{b(x)}(x\in S)\)

那麼咱們猜想一個最優值λ,從新構造一個函數:

\(g(\lambda)=max\lbrace a(x)-\lambda b(x)\rbrace\)

若咱們設一個\(\lambda^*=f(x^*)\)爲最優解,那麼則有:

\(g(\lambda)<0⇔\lambda>\lambda^*\)

\(g(\lambda)=0⇔ \lambda=\lambda^*\)

\(g(\lambda)>0⇔ \lambda<\lambda^*\)

彷佛貌似就是大於小於的狀況要麼不合法,要麼不夠優。當g(λ)=0時正好取到最優解。

而後咱們開始思考如何搞出來,能夠發現咱們選擇一條邊就要選擇與它相連的兩個端點。那麼,隨便YY一下,搞一個如上面的建圖,就能夠發現上面那個建圖知足咱們的條件。

但是有個問題,當λ取到必定值的時候,它變大並不會影響最大權閉合圖的值,通通都是0.。因此咱們二分求得是在g(λ)=0時最小的λ。

9.疏散模型

這個模型就是我本身YY出來的了。前面應該提到過了,我作過一道叫緊急疏散的鬼畜題。這道題讓我生生調了半天代碼,今後對這題印象深入。

那麼這是一個什麼樣的模型呢?它利用兩個特色,一個是分層圖的一些概念,一個是先後綴的思想。明顯的是,分層圖咱們可能在一些題目中看出,而後坑爹的是,這個題分層的話整個題目複雜度極高,時間空間都不支持。這個時候能夠嘗試只拆開匯點,也就是我以前所說的拆門。而後對於那個時間點能到匯點的點,與這個時刻的門連一條邊。而後利用前綴思想,把每一天的匯點一串inf連下去,最後就能夠求出。並且時空複雜度都很小啊。

10.優化建圖

有這麼些題,你一看就知道應該怎麼建圖,而後仔細看看數據範圍,用常規方法不是炸你空間就是讓你T掉。。。。這個時候咱們能夠嘗試一下優化,好比說使用數據結構。雖然這東西放出來以後通常人不會願意打。那麼是什麼原理呢?若是咱們某個區間裏的每一個點都要互相連邊,那麼就能夠建線段樹,而後把對應的區間連邊,而後線段樹的節點之間互相連邊,就能夠優化邊數。

然而值得注意的是,我並無寫數據結構/優化建圖,由於更不正常的題目,咱們或許會用到計算幾何。十分鬼畜,鬼畜至極。。然而這種題目。。鬼才會去寫啊。。。有興趣的去看看[Jsoi2018]絕地反擊 。話說這個優化的原理是什麼呢?舉個栗子:假設一個題目有不少點,可是你通過各類手玩和證實以後,發現全部不在凸包上的點不會互相連邊,這個時候求一發凸包,就能夠大大的減小邊數了。

11.最長反鏈和最小鏈覆蓋

有向無環圖中,鏈是一個點的集合,這個集合中任意兩個元素v、u,要麼v能走到u,要麼u能走到v。

反鏈是一個點的集合,這個集合中任意兩點誰也不能走到誰。

最長反鏈是反鏈中最長的那個。

那怎麼求呢?

11.1 傳遞閉包

在開始介紹這個以前,我先簡單敘述一下傳遞閉包是個什麼東西。能夠發現的是百度百科裏的東西都奇怪的一批根本讓人看不懂。那麼我就儘可能通俗的說一下。

傳遞閉包就是求一個圖裏面全部知足傳遞性的點。那麼傳遞性又是什麼呢?簡單來講就是:假設圖中對於圖中的節點\(i\),若果有一個j點能夠到\(i\)\(i\)點又能夠到\(k\),那麼\(j\)就能到\(k\)。這樣的節點就能夠說是有傳遞性。看上去很是的,簡單。其實就是這樣。求完傳遞閉包以後,咱們可以知道的是圖中的任意兩點是否相連。

那麼,咱們能夠用Floyd算法搞出來,沒錯,就是Floyd。只不過咱們如今是求兩點是否相連,而不是求其最短路徑。

11.2最小鏈覆蓋

這個東西就是求出最長反鏈的關鍵。由於:最小鏈覆蓋=最長反鏈長度。

那麼,最小鏈覆蓋又怎麼求出呢?

回想一下咱們以前是否是有一個地方介紹過了最小路徑覆蓋。那個地方所求的是不能相交的最小路徑覆蓋,那麼這裏咱們要求的就是路徑能夠相交的最小路徑覆蓋。

問題又來了,這個又怎麼求解呢?

咱們把原來的圖作一遍傳遞閉包,而後若是任意兩點\(x,y\),知足\(x\)能夠到達\(y\),那麼咱們就在拆點後的二分圖裏面連一條\((x_1,y_2)\)的邊。

這樣球能夠繞過一些在原圖中被其餘路徑佔用的點,構造新的路徑從而求出最小路徑覆蓋。

那麼這樣咱們就把能夠相交的最小路徑覆蓋轉化爲了路徑不能相交的最小路徑覆蓋了。 從而咱們求出了這個圖的最小鏈覆蓋。

11.3 十分感性的證實

那麼咱們證實一下剛纔一個結論的正確性。

咱們經過觀察定義能夠得出,最長反鏈的點必定在不一樣的鏈裏。因此最長反鏈長度\(\le\)最小鏈覆蓋數。

那麼咱們接着能夠經過感性理解和數學概括證出最長反鏈長度\(\ge\)最小鏈覆蓋數.

如此咱們就可以得證了。

11.4最長鏈長度 = 最小反鏈覆蓋數

不想證了。這個結論就是上面的反向結論,其實直接背過就能夠了。。。。。

12.混合圖的歐拉回路

12.1歐拉回路

(1)給定一個圖G,假設咱們能一筆畫,若是圖G中的一個路徑包括每一個邊剛好一次,則該路徑稱爲歐拉路徑(Euler path)。若是一個迴路是歐拉路徑,則稱爲歐拉回路(Euler circuit)。
具備歐拉回路的圖稱爲歐拉圖(簡稱E圖)。具備歐拉路徑但不具備歐拉回路的圖稱爲半歐拉圖。

(2)對於一個無向圖,若是每一個點的度數均爲偶數,那麼這個圖是一個歐拉圖。

(3)對於一個有向圖,若是每一個點的出度都等於入度,那麼這個圖是一個歐拉圖。

(4)對於無向圖,若是每一個點度數均爲偶數,或者有且僅有兩個頂點度數爲奇數,那麼這個圖中存在歐拉路徑。

(5)對於有向圖,若是每一個點的出度等於入度,或者有且僅有兩個點不符,且這兩個點一個入度比出度小1,一個出度比入度小1,則這個圖存在歐拉路徑。

12.2求解通常歐拉圖

直接暴力枚舉全部點的出度入度便可,用上面的關係進行判斷,能夠求出。

12.3求解混合歐拉圖

明顯,不能經過剛剛的方法來套用到這個題上去。

對於這種圖,咱們首先能夠自定向無向邊的方向。能夠rand,也能夠規定一個方向從而所有指向一個方向。不過這樣沒法直接獲得答案,說不定臉好,可是咱們能夠經過這樣一個操做搞出來一個徹底有向的圖,能夠套用上面的性質來初步判斷這個題是否有歐拉回路。不過這樣作明顯沒有依據,只是一個預處理的過程,再經過調整判斷是否有解。

那麼如何自調整呢?引用一段話:

所謂的自調整方法就是將其中的一些邊的方向調整回來,使全部的點的出度等於入度。可是有一條邊的方向改變後,可能會改變一個點的出度的同時改變另外一個點的入度,至關於一條邊制約着兩個點。同時有些點的出度大於入度,迫切但願它的某些點出邊轉向;而有些點的入度大於出度,迫切但願它的某些入邊轉向。這兩條邊雖然需求不一樣,可是他們之間每每一條邊轉向就能同時知足兩者。

而後咱們發現,網絡流貌似能夠知足這個自調整的性質。

12.4算法步驟

1.\(首先對於每一個入度>出度的點,咱們將其與源點S鏈接一條權值爲\frac{入度-出度}{2}的邊;\)

\(對於每一個入度<出度的點,咱們將其與匯點T鏈接一條權值爲\frac{出度-入度}{2}的邊。\)

爲何除以2,由於咱們改變一條邊的方向時,會形成出度和入度的改變,因此。。。

2.而後將原圖中全部你定了向的無向邊鏈接的兩個點連一條邊權爲1,方向爲你定向方向的無向邊。

3.跑網絡流去,若是滿流天然就有解。

4.把在網絡流中那些由於原圖無向邊而建的流量爲1的邊中通過流量的邊反向,就造成了一個能跑出歐拉回路的有向圖,若是要求方案,用有向圖求歐拉回路的方法求解便可 。

13.最大權閉合子圖

發現前面寫最小割的時候把這個東西跳過去了,。。。如今補上。

13.1定義

一個子圖(點集), 若是它的全部的出邊所指向的點都在這個子圖當中,那麼它就是閉合子圖。  點權和最大的閉合子圖就是最大閉合子圖。

13.2構圖

設置超級源匯S,T。

而後使S和全部的正權的點鏈接權值爲點權的邊,全部點權爲負的點和T鏈接權值爲點權絕對值的邊。而後若是選擇了某個v點才能夠選u點,那麼把u向v鏈接一條權值爲\(\infty\)的邊。

而後隨便跑跑網絡流。

最大點權和=正點權和-最小割。

13.3並不感性的證實

首先說簡單割,就是咱們求最小割的時候每條割邊都與S或T相連。在閉合圖中,因爲咱們不與S或T鏈接的邊的權值都是inf,因此咱們不會割到他們。容易得證閉合圖中的最小割是簡單割。

接着證實簡單割和閉合圖的關係。

由於簡單割不會含有那些正無窮的邊,因此不含有連向另外一個集合(除T)的點,因此其出邊的終點都在簡單割中,知足閉合圖定義。

咱們假設跑完最小割以後的圖中一共有兩部分點\(X,Y\)\(X\)表示與S相連的點,\(Y\)表示與T相連的點,那麼咱們能夠設\(X_0,Y_0\)爲負權點,\(X_1,Y_1\)爲正權點。而後因爲咱們求了一遍最小割,根據上面簡單割的證實咱們發現不會割去中間inf的邊,因此咱們能夠求出:

\(最小割=|X_0|+|Y_1|\)

剛纔已經證實過X,Y均是閉合圖。

而後咱們能夠發現X的權值和能夠表示爲\(Sum_X=X_1-|X_0|\)

咱們把上面那個式子代入到這個式子裏。

\(Sum_X+最小割=X_1-|X_0|+|X_0|+Y_1\)

而後咱們得出:

\(Sum_X+最小割=X_1+Y_1=原圖全部正權點權值之和\)

而後咱們得證。

14.文理分科模型

原本不想整理這個,由於確實不是特別的突出,用的地方也不是不少。而後,筆者在寫這個東西的這一天連着看見兩道這樣的題。因此仍是整理一下。

這裏表示若是點的貢獻不只僅是點權,並且還有附加的條件的話,好比同時選A,同時選B,選擇不相同。。。。。

那麼咱們就利用最小割的性質構造一下便可。用「割掉」表明選擇。

而後分狀況討論全部選擇狀況,使對應狀況下被割掉的邊流量之和等於權值 便可。


到這裏這個課件的系統知識部分就已經結束了,然而仍是有不小的漏洞,好比線性規劃,好比有上下界的網絡流,好比流量平衡等等。不過筆者自信本身的絕對是目前網上能找到的最全的一個了。

10、幾道腦洞大開的題目

這些題大多比較新,都是這兩年的新題,而且建模的思路極其鬼畜新穎,因此仍是很值得一看的。不,是很值得一看,並且我在這裏寫的題解確實,十分詳細。。。。


1.[SDOI2017]新生舞會

題面描述

學校組織了一次新生舞會,Cathy做爲經驗豐富的老學姐,負責爲同窗們安排舞伴。

有nn 個男生和nn 個女生參加舞會買一個男生和一個女生一塊兒跳舞,互爲舞伴。

Cathy收集了這些同窗之間的關係,好比兩我的以前認識沒計算得出 \(a_{i,j}\)

Cathy還須要考慮兩我的一塊兒跳舞是否方便,好比身高體重差異會不會太大,計算得出 \(b_{i,j}\) ,表示第i個男生和第j個女生一塊兒跳舞時的不協調程度。

固然,還須要考慮不少其餘問題。

Cathy想先用一個程序經過\(a_{i,j}\)\(b_{i,j}\) 求出一種方案,再手動對方案進行微調。

Cathy找到你,但願你幫她寫那個程序。

一個方案中有n對舞伴,假設沒對舞伴的喜悅程度分別是\(a'_1,a'_2,...,a'_n\),假設每對舞伴的不協調程度分別是\(b'_1,b'_2,...,b'_n\) 。令

C=\(\frac{a'_1+a'_2+...+a'_n}{b'_1+b'_2+...+b'_n}′\)

Cathy但願C值最大。

數據範圍

對於100%的數據,\(1\le n\le 100,1\le a_{i,j},b_{i,j}<=10^4\)

Solution

這個題乍一看上去非常讓人一籌莫展,DP明顯被強大的後效性D掉了,聽說有線性規劃的作法,可是筆者並無想出來,搜索的話n=100讓人非常頭疼。那麼如今沒有什麼辦法,咱們就只能再回到題目裏面去看看有沒有什麼沒有發現的信息。

而後咱們發現了這個東西:C=\(\frac{a'_1+a'_2+...+a'_n}{b'_1+b'_2+...+b'_n}′\)

咱們發現這個分式必定有意義,那麼分母不可能爲0,又由於給的都是正整數,因此分母大於0。

那麼首先咱們先去分母,這個式子就變成了:\((b'_1+b'_2+\cdots+d'_n)\times C=(a'_1+a'_2+\cdots+a'_n)\)

而後咱們移項,它就成了\((a'_1+a'_2+\cdots+a'_n)-(b'_1+b'_2+\cdots+d'_n)\times C=0\)

而後咱們去括號,就變成了:\(a'_1+a'_2+\cdots+a'_n-b'_1\times C-b'_2\times C-\cdots-b'_n\times C=0\)

爲了方便計算,咱們能夠合併一下同類項:\((a'_1-b'_1\times C)+(a'_2-b'_2\times C)+\cdots+(a'_n-b'_n\times C)=0\)

咱們題目中要求的式子就和如今的式子同樣了。

此時咱們就能夠二分C值,而後判斷tot是否大於或小於0來縮小邊界。

而後咱們發現每一個{a,b}數對的下標都是一一對應的,因此咱們直接從第i個男生想第j個女生鏈接一條\(a_i-b_i\times C\)的邊就能夠了。

說了從這裏開始就會有代碼。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#define inf 1000000000000001ll
#define maxxx 500000001
#define re register
#define ll long long
#define min(a,b) a<b?a:b 
using namespace std;
const long double eps=0.00000007;
struct po{
    int to,nxt,w;
    ll dis;
};
po edge[800001];
int n,m,s,t,b[205],p;
int head[205],num=-1;
ll tot,dis[205],pa[501][501],pb[501][501];
inline int read()
{
    int x=0,c=1;
    char ch=' ';
    while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
    while(ch=='-')c*=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-'0',ch=getchar();
    return x*c;
}
inline void add_edge(int from,int to,int w,ll dis)
{
    edge[++num].nxt=head[from];
    edge[num].to=to;
    edge[num].w=w;
    edge[num].dis=dis;
    head[from]=num;
}
inline void add(int from,int to,int w,ll dis)
{
    add_edge(from,to,w,dis);
    add_edge(to,from,0,-dis);
}
inline bool spfa()
{
    for(re int i=s;i<=t;i++) dis[i]=inf+1;
    memset(b,0,sizeof(b));
    queue<int> q;
    q.push(t);
    dis[t]=0;
    b[t]=1;
    while(!q.empty()){
        int u=q.front();
        q.pop();
        b[u]=0;
        for(re int i=head[u];i!=-1;i=edge[i].nxt){
            int v=edge[i].to;
            if(edge[i^1].w>0&&dis[v]>dis[u]-edge[i].dis){
                dis[v]=dis[u]-edge[i].dis;
                if(!b[v]){
                    b[v]=1;
                    q.push(v);
                }
            }
        }
    }
    return dis[s]<inf;
}
inline int dfs(int u,int low)
{
    b[u]=1;
    if(u==t) return low;
    int diss=0;
    for(re int i=head[u];i!=-1;i=edge[i].nxt){
        int v=edge[i].to;
        if(edge[i].w&&!b[v]&&dis[v]==dis[u]-edge[i].dis){
            int check=dfs(v,min(edge[i].w,low));
            if(check){
                tot+=check*edge[i].dis;
                low-=check;
                diss+=check;
                edge[i].w-=check;
                edge[i^1].w+=check;
                if(low==0) break;
            }
        }
    }
    return diss;
}
inline void max_flow()
{
    int ans=0;
    while(spfa()){
        b[t]=1;
        while(b[t]){
            memset(b,0,sizeof(b));
            ans+=dfs(s,maxxx);

        }
    }
    return;
}
inline void build(ll x)
{
    memset(head,-1,sizeof(head));
    num=-1;tot=0;
    for(re int i=1;i<=n;i++)
    add(s,i,1,0),add(n+i,t,1,0);
    for(re int i=1;i<=n;i++)
     for(re int j=1;j<=n;j++)
     add(i,j+n,1,-(pa[i][j]-pb[i][j]*x));
}
inline bool check(ll x)
{
    build(x);
    max_flow();
    if(-tot<=0) return 1;
    else return 0;
}
int main()
{
    n=read();
    for(re int i=1;i<=n;i++) 
     for(re int j=1;j<=n;j++)
     pa[i][j]=read(),pa[i][j]*=5000000;
    for(re int i=1;i<=n;i++)
     for(re int j=1;j<=n;j++)
     pb[i][j]=read();
     s=0,t=n+n+1;
    ll l=1,r=50000000000ll;
    while(r>=l){
        ll mid=(l+r)/2;
        if(check(mid))
        r=mid-1; else
        l=mid+1;
    }
    printf("%.6lf",l*1.0/5000000);
}

這個題要用數組寫費用流,卡告終構體。。。。

2.[2018多省省隊聯測]劈配

這是省選考試題了,惋惜當時並無想到最後。。。

題目描述

總共n名參賽選手(編號從1至n)每人寫出一份代碼並介紹本身的夢想。接着由全部導師對這些選手進行排名。

爲了不後續的麻煩,規定不存在排名並列的狀況。

同時,每名選手都將獨立地填寫一份志願表,來對總共m位導師(編號從1至m)做出評價。

志願表上包含了共m檔志願。

對於每一檔志願,選手被容許填寫最多C位導師,每位導師最多被每位選手填寫一次(放棄某些導師也是被容許的)。

在雙方的工做都完成後,進行錄取工做。

每位導師都有本身戰隊的人數上限,這意味着可能有部分選手的較高志願、甚至是所有志願沒法獲得知足。節目組對」

前i名的錄取結果最優「做出以下定義:

前1名的錄取結果最優,當且僅當第1名被其最高非空志願錄取(特別地,若是第1名沒有填寫志願表,那麼該選手出局)。

前i名的錄取結果最優,當且僅當在前i-1名的錄取結果最優的狀況下:第i名被其理論可能的最高志願錄取

(特別地,若是第i名沒有填寫志願表、或其全部志願中的導師戰隊均已滿員,那麼該選手出局)。

若是一種方案知足‘‘前n名的錄取結果最優’’,那麼咱們能夠簡稱這種方案是最優的。

舉例而言,2位導師T老師、F老師的戰隊人數上限分別都是1人;2位選手Zayid、DuckD分列第一、2名。

那麼下面3種志願表及其對應的最優錄取結果如表中所示:

img

能夠證實,對於上面的志願表,對應的方案都是惟一的最優錄取結果。

每一個人都有一個本身的理想值si,表示第i位同窗但願本身被第si或更高的志願錄取,若是沒有,那麼他就會很是沮喪。

如今,全部選手的志願表和排名都已公示。巧合的是,每位選手的排名都剛好與它們的編號相同。

對於每一位選手,Zayid都想知道下面兩個問題的答案:

在最優的錄取方案中,他會被第幾志願錄取。

在其餘選手相對排名不變的狀況下,至少上升多少名才能使得他不沮喪。

做爲《中國新代碼》的實力派代碼手,Zayid固然輕鬆地解決了這個問題。

不過他仍是想請你再算一遍,來檢驗本身計算的正確性。

數據範圍

img

Solution

這個題剛拿到題我就基本肯定網絡流算法了。。你看這名字,劈配,多麼的人性化,直接把算法告訴你。而後開始思考建模,最後崩潰。

那麼這個題應該怎麼作呢,咱們一步一步的分析:

2.1面向小範圍數據

首先咱們能夠發現有那麼一部分數據的範圍真是有夠小的,那麼咱們能夠搜索,暴力查找出全部可能的方案,而後選取最優的一個,就能夠先把1,2,3這三個數據點的分拿到手。

2.2針對數據特色

咱們能夠發現這些數據總共有兩個特色,一個是C的特殊性,一個是b的特殊性。

針對C的特殊性,咱們發現能夠有一個貪心,就是最高的開始一個一個選,若是後面有不能選的,那麼他必定就沒人可選,而後暴力求解出第二問就能夠很穩的拿到4,5,6,7這40分。

而後針對b的特殊性,嗯,有一個方法來着,動態加邊的二分圖匹配,這樣又能夠獲得第9個點的10pts。

2.3 各類僞算法

而後就是各類奇奇怪怪的算法了,好比我考場上寫的費用流,回來用mhr的思路寫的拆點的網絡流,還有什麼極其暴力的複雜度很高的匈牙利什麼的,它們都能過一部分點,可是全都不行。這裏介紹一下mhr思路的那個作法,實際是可行的,只是我第二問作錯了,他的第二問寫的太醜T了而已。

很明顯c=1的能夠隨便過去,那麼C=其餘 的呢?咱們考慮拆點,把每個人拆成總共志願個數個點,把全部導師向匯點連邊,全部的志願都和導師連邊。而後對每個人動態加邊,就是這我的先和他的第一志願連邊,而後判斷是否有流量增量,若是沒有,就先把這個邊刪了,和下一個志願連邊,這樣針對任何一我的,他前面是已經連好的最優的狀況,而且因爲邊的限制,它不會影響前面的最優狀況。這樣跑到最後咱們就會獲得一個最優解。

而且因爲同一個時刻咱們這裏面最多有nm條邊,因此並不會有時間複雜度問題,這樣第一問就作出來,而後考慮第二問,咱們枚舉全部不滿意的人,使用二分,二分這我的前進到多少名能夠保證他可以知足要求。可是這樣咱們每一次都要從新建圖,時間複雜度大概是\(O(nlogn\times n^2m)\),看上去問題很嚴重,當初我和mhr是這麼說的,你不用跑網絡流,你看你那裏有個memset,你光這麼多遍memset就超時了。。。

可是咱們在通過一些剪枝優化以後,實際上的複雜度遠遠沒有這麼多,就能夠在一個合理的時間範圍內獲得答案。而後估計能夠是最優解最後一名了。。。

由於我第二問求錯了,mhr寫的太醜T了,因此這個方法就不放代碼了。

2.4正解算法

在上一個mhr算法中,咱們能夠發現每個人實際上只有一個利用的點,也就是說最後的圖裏面咱們一共有\((n-1)\times m\)這麼多的點白白浪費掉了。因而能夠考慮能不能不拆點,找到一些其餘的方法來實現那個過程。而後發現剛纔拆點的目的是爲了在後面的人選的時候不讓前面的人選到更差的。最後刪邊完善了這個操做。可是實際上咱們能夠經過從新建圖來達到一樣的效果。能夠發現的是第一問裏面咱們是很是嚴格的按照排名選的,一旦一我的的志願等級肯定了,那麼就必定不會更改。而且這我的選的志願等級咱們已經保存在了輸出數組裏面。每一次直接從新調用就能夠了。能夠發現雖然多了一個十分繁瑣的從新建圖的過程,可是並無將他的複雜度提高太多。

而後對於第二問,咱們依然枚舉n而後二分,可是如今的是時間複雜度變成了\(O(nlogn\times nm)\)的,就能夠跑過去了。

代碼以下了:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<queue>
#define re register
#define inf 50000001
#define ll long long
#define min(a,b) a<b?a:b
#define max(a,b) a>b?a:b
using namespace std;
struct po{
    int nxt,to,w;
};
po edge[6000001];
int T,C,n,m,s,t;
int head[501],dep[501],num=-1,b[205],want[202];
int a[205][205],rs[205][205],ql[202],ans,cur[501],last,tag;
int out1[201],out2[201];
inline int read()
{
    int x=0,c=1;
    char ch=' ';
    while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
    while(ch=='-')c*=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-'0',ch=getchar();
    return x*c;
}
inline void add_edge(int from,int to,int w)
{
    edge[++num].nxt=head[from];
    edge[num].to=to;
    edge[num].w=w;
    head[from]=num;
}
inline void add(int from,int to,int w)
{
    add_edge(from,to,w);
    add_edge(to,from,0);
}
inline bool bfs()
{
    memset(dep,0,sizeof(dep));
    queue<int> q;
    while(!q.empty())
    q.pop();
    q.push(s);
    dep[s]=1;
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        for(re int i=head[u];i!=-1;i=edge[i].nxt)
        {
            int v=edge[i].to;
            if(dep[v]==0&&edge[i].w>0)
            {
                dep[v]=dep[u]+1;
                if(v==t)
                return 1;
                q.push(v);
            }
        }
    }
    return 0;
}
inline int dfs(int u,int dis)
{
    if(u==t) return dis;
    int diss=0;
    for(re int& i=cur[u];i!=-1;i=edge[i].nxt){
        int v=edge[i].to;
        if(edge[i].w!=0&&dep[v]==dep[u]+1){
            int check=dfs(v,min(dis,edge[i].w));
            if(check>0)
            {
                dis-=check;
                diss+=check;
                edge[i].w-=check;
                edge[i^1].w+=check;
                if(dis==0) break;
            }
        }
    }
    return diss;
}
inline void dinic()
{
    while(bfs())
    {
        for(re int i=s;i<=t;i++)
            cur[i]=head[i];
        while(int d=dfs(s,inf)) ans+=d;
    }
}
void init(){
    memset(out1,0,sizeof(out1));
    memset(out2,0,sizeof(out2));
    n=read();m=read();
    s=0;t=m+n+1;
    for(re int i=1;i<=n;i++) out1[i]=m+1;
    for(re int i=1;i<=m;i++) b[i]=read();
    for(re int i=1;i<=n;i++)
        for(re int j=1;j<=m;j++)
            a[i][j]=read();        
    for(re int i=1;i<=n;i++) want[i]=read();
}
int main()
{
    //freopen("date.in","r",stdin);
    T=read();C=read();
    while(T--){
        init();
        for(re int i=1;i<=n;i++){
            num=-1;memset(head,-1,sizeof(head));
            for(re int j=1;j<=n;j++){if(j==i) tag=num+1;add(s,j,1);}
            for(re int j=1;j<=m;j++) add(n+j,t,b[j]);
            for(re int j=1;j<i;j++)
                for(re int l=1;l<=m;l++)
                    if(a[j][l]==out1[j]) add(j,n+l,1);
            dinic();
            for(re int l=1;l<=m;l++){
                for(re int j=1;j<=m;j++)
                    if(a[i][j]==l) add(i,n+j,1);
                dinic();
                if(edge[tag].w==0){out1[i]=l;break;}
            }
        }
        for(re int i=1;i<=n;i++){
            if(out1[i]<=want[i]) continue;
            int l=1,r=i-1;out2[i]=i;
            while(l<=r){
                num=-1;memset(head,-1,sizeof(head));
                for(re int j=1;j<=n;j++){if(j==i) tag=num+1;add(s,j,1);}
                for(re int j=1;j<=m;j++) add(n+j,t,b[j]);
                int mid=l+r>>1;
                for(re int j=1;j<mid;j++)
                    for(re int l=1;l<=m;l++)
                        if(a[j][l]==out1[j]) add(j,n+l,1);
                dinic();
                for(re int j=1;j<=m;j++)
                    if(a[i][j]>0&&a[i][j]<=want[i]) add(i,n+j,1);
                dinic();
                if(edge[tag].w==0) {l=mid+1;out2[i]=i-mid;}
                else r=mid-1;
            }
        }
        for(re int i=1;i<=n;i++)
        cout<<out1[i]<<" ";
        cout<<endl;
        for(re int i=1;i<=n;i++)
        cout<<out2[i]<<" ";
        cout<<endl;    
    }
}

3.[2017國家集訓隊測試]無限之環

題目描述

曾經有一款流行的遊戲,叫作InfinityLoop,先來簡單的介紹一下這個遊戲:
遊戲在一個n×m的網格狀棋盤上進行,其中有些小方格中會有水管,水管可能在方格某些方向的邊界的中點有接口
,全部水管的粗細都相同,因此若是兩個相鄰方格的公共邊界的中點都有接頭,那麼能夠看做這兩個接頭互相鏈接
。水管有如下15種形狀:
img

遊戲開始時,棋盤中水管可能存在漏水的地方。
形式化地:若是存在某個接頭,沒有和其它接頭相鏈接,那麼它就是一個漏水的地方。
玩家能夠進行一種操做:選定一個含有非直線型水管的方格,將其中的水管繞方格中心順時針或逆時針旋轉90度。
直線型水管是指左圖裏中間一行的兩種水管。
現給出一個初始局面,請問最少進行多少次操做能夠使棋盤上不存在漏水的地方。

第一行兩個正整數n,m表明網格的大小。
接下來n行每行m數,每一個數是[0,15]中的一個
你能夠將其看做一個4位的二進制數,從低到高每一位分別表明初始局面中這個格子上、右、下、左方向上是否有水管接頭。
特別地,若是這個數是000,則意味着這個位置沒有水管。
好比3(0011(2))表明上和右有接頭,也就是一個L型,而12(1100(2))表明下和左有接頭,也就是將L型旋轉180度

數據範圍

\(n×m≤2000\)

Solution

說實話這個題真的很差作,我本身也是調了一個上午,代碼量要好調的就很大,可是縮減代碼長度的話錯了又很差調。。很噁心的一個題。

3.1肯定算法

有誰能夠一眼看出來這題網絡流我真的服他,牆都不服就服他。這個題筆者仔細揣摩了很久才肯定是費用流無疑。

具體怎麼看出來的,我把個人分析過程跟你們說一下:首先考慮這個題最可能的算法,看到這種狀態不少的題目首先就是思考能不能搜索,而後發現複雜度過高過不去。接着就是動態規劃,筆者是沒想出來,可是有一個大概的動態規劃的構思,就是根據水管的種類其實不多,一共就15種,可不能夠搞一個狀壓DP試試,讀者有興趣的能夠本身思考。而後這題不多是數據結構,那排除了一切不可能的狀況以後,剩下的再不可能也是可能的了。因而思考網絡流。

咱們能夠這麼考慮,只要一個格子有了一個頭,那麼它就必須和旁邊的一個格子裏的頭對應起來,不然就不能造成通路。全部格子的任何一個頭都知足這個道理。那麼咱們就須要讓他們所有匹配。

嗯,匹配,彷佛和網絡流沾上點邊了。

3.2問題簡化1

能夠先考慮簡單的版本,若是就是給了你這些水管的狀態,也沒讓你轉動他們,你是否可以用網絡流算出來它能不能造成一條通路呢?答案是能夠,咱們發現一個地方的水管只和它上下左右的水管有着直接的聯繫。這樣咱們就把這個簡化過的問題精簡成了一個經典的網絡流題目的模型,沒錯,黑白染色模型。咱們拆點以後求最大流是不是水管的接頭數就能夠了。

3.3問題簡化2

咱們把這個簡單的版本昇華一下,如今你能夠轉動這些水管了,咱們能不能使它造成一個通路呢?答案仍然是可行的,咱們只須要拆完點以後把能夠轉換到的狀態之間互相連邊就能夠了。仍然是求最大流是不是水管接頭數。

3.4回到題目中

如今咱們回到原來的題目中,不難發現,咱們只要在問題簡化2的基礎上增長一個費用就能夠了。那麼咱們如何設置這些費用呢?

很明顯這個題有三種不一樣樣式的格子,一個是死衚衕形,一個是L形,還有一個T形。剩下的兩種要麼不能動,要麼動了和沒動同樣,不須要討論。
對於死衚衕形,很明顯轉到旁邊須要1的花費,而轉到對面須要2的花費。
對於L形,不大明顯的是咱們轉動90度就至關於把一條邊轉到了對面,而180度就是徹底旋轉過去。。。
對於T形,能夠發現他就是一個反過來的死衚衕形,反過來連邊就能夠了。。

那麼這個題就能夠套用費用流的板子了:

#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#define inf 1000000001
#define re register
#define ll long long
#define min(a,b) a<b?a:b
#define MAXN 20001
#define MAXM 40000005
#define id1 nm[i][j]
#define id2 (nm[i][j]+(n*m))
#define id3 (nm[i][j]+(n*m*2))
#define id4 (nm[i][j]+(n*m*3))
#define id5 (nm[i][j]+(n*m*4))
using namespace std;
int n,m,s,t;
int head[MAXN],num=-1,tot,dis[MAXN],b[MAXN],maxx;
int to[100005],nxt[100005],w[100005],edis[100005];
int a[2001][2001],nm[2001][2001];
inline int read()
{
    int x=0,c=1;
    char ch=' ';
    while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
    while(ch=='-')c*=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-'0',ch=getchar();
    return x*c;
}
inline void add_edge(int from,int too,int ww,ll dis)
{
    nxt[++num]=head[from];
    to[num]=too;
    w[num]=ww;
    edis[num]=dis;
    head[from]=num;
}
inline void add(int from,int too,int ww,ll dis)
{
    add_edge(from,too,ww,dis);
    add_edge(too,from,0,-dis);
}
inline bool spfa()
{
    for(re int i=s;i<=t;i++) dis[i]=inf+1;
    memset(b,0,sizeof(b));
    deque<int> q;
    q.push_back(t);
    dis[t]=0;
    b[t]=1;
    while(!q.empty()){
        int u=q.front();
        q.pop_front();
        b[u]=0;
        for(re int i=head[u];i!=-1;i=nxt[i]){
            int v=to[i];
            if(w[i^1]>0&&dis[v]>dis[u]-edis[i]){
                dis[v]=dis[u]-edis[i];
                if(!b[v]){
                    b[v]=1;
                    if(!q.empty()&&dis[v]<dis[q.front()])
                    q.push_front(v);
                    else
                    q.push_back(v);
                }
            }
        }
    }
    return dis[s]<inf;
}
inline int dfs(int u,int low)
{
    b[u]=1;
    if(u==t) return low;
    int diss=0;
    for(re int i=head[u];i!=-1;i=nxt[i]){
        int v=to[i];
        if(w[i]&&!b[v]&&dis[v]==dis[u]-edis[i]){
            int check=dfs(v,min(w[i],low));
            if(check){
                tot+=check*edis[i];
                low-=check;
                diss+=check;
                w[i]-=check;
                w[i^1]+=check;
                if(low==0) break;
            }
        }
    }
    return diss;
}
inline int max_flow()
{
    int ans=0;
    while(spfa()){
        b[t]=1;
        while(b[t]){
            memset(b,0,sizeof(b));
            ans+=dfs(s,inf);    
        }
    }
    return ans;
}
int main() 
{
    //freopen("date.in","r",stdin);
    memset(head,-1,sizeof(head));
    n=read();m=read();
    for(re int i=1;i<=n;i++)
        for(re int j=1;j<=m;j++)
            a[i][j]=read(),nm[i][j]=(i-1)*m+j;
    s=0;t=5*n*m+1;
    for(re int i=1;i<=n;i++)
        for(re int j=1;j<=m;j++){
            if((i+j)%2==0) add(s,nm[i][j],inf,0);
            else add(nm[i][j],t,inf,0);
        }
    for(re int i=1;i<=n;i++){
        for(re int j=1;j<=m;j++)
        if((i+j)%2==0){
            if(a[i][j]==1){add(id1,id2,1,0);add(id2,id3,1,1);add(id2,id5,1,1);add(id2,id4,1,2);maxx++;}
            if(a[i][j]==2){add(id1,id3,1,0);add(id3,id2,1,1);add(id3,id4,1,1);add(id3,id5,1,2);maxx++;}
            if(a[i][j]==4){add(id1,id4,1,0);add(id4,id3,1,1);add(id4,id5,1,1);add(id4,id2,1,2);maxx++;}
            if(a[i][j]==8){add(id1,id5,1,0);add(id5,id2,1,1);add(id5,id4,1,1);add(id5,id3,1,2);maxx++;}
            if(a[i][j]==3){add(id1,id2,1,0);add(id1,id3,1,0);add(id2,id4,1,1);add(id3,id5,1,1);maxx+=2;}
            if(a[i][j]==6){add(id1,id3,1,0);add(id1,id4,1,0);add(id3,id5,1,1);add(id4,id2,1,1);maxx+=2;}
            if(a[i][j]==9){add(id1,id5,1,0);add(id1,id2,1,0);add(id5,id3,1,1);add(id2,id4,1,1);maxx+=2;}
            if(a[i][j]==12){add(id1,id4,1,0);add(id1,id5,1,0);add(id4,id2,1,1);add(id5,id3,1,1);maxx+=2;}
            if(a[i][j]==7){add(id1,id2,1,0);add(id1,id3,1,0);add(id1,id4,1,0);add(id2,id5,1,1);add(id3,id5,1,2);add(id4,id5,1,1);maxx+=3;}
            if(a[i][j]==11){add(id1,id2,1,0);add(id1,id3,1,0);add(id1,id5,1,0);add(id2,id4,1,2);add(id3,id4,1,1);add(id5,id4,1,1);maxx+=3;}
            if(a[i][j]==13){add(id1,id2,1,0);add(id1,id4,1,0);add(id1,id5,1,0);add(id2,id3,1,1);add(id4,id3,1,1);add(id5,id3,1,2);maxx+=3;}
            if(a[i][j]==14){add(id1,id3,1,0);add(id1,id4,1,0);add(id1,id5,1,0);add(id3,id2,1,1);add(id4,id2,1,2);add(id5,id2,1,1);maxx+=3;}         
            if(a[i][j]==5){add(id1,id2,1,0);add(id1,id4,1,0);maxx+=2;}
            if(a[i][j]==10){add(id1,id3,1,0);add(id1,id5,1,0);maxx+=2;}
            if(a[i][j]==15){add(id1,id2,1,0);add(id1,id3,1,0);add(id1,id4,1,0);add(id1,id5,1,0);maxx+=4;}
        } else {
            if(a[i][j]==1){add(id2,id1,1,0);add(id3,id2,1,1);add(id5,id2,1,1);add(id4,id2,1,2);maxx++;}
            if(a[i][j]==2){add(id3,id1,1,0);add(id2,id3,1,1);add(id4,id3,1,1);add(id5,id3,1,2);maxx++;}
            if(a[i][j]==4){add(id4,id1,1,0);add(id3,id4,1,1);add(id5,id4,1,1);add(id2,id4,1,2);maxx++;}
            if(a[i][j]==8){add(id5,id1,1,0);add(id2,id5,1,1);add(id4,id5,1,1);add(id3,id5,1,2);maxx++;}
            if(a[i][j]==3){add(id2,id1,1,0);add(id3,id1,1,0);add(id4,id2,1,1);add(id5,id3,1,1);maxx+=2;}
            if(a[i][j]==6){add(id3,id1,1,0);add(id4,id1,1,0);add(id5,id3,1,1);add(id2,id4,1,1);maxx+=2;}
            if(a[i][j]==9){add(id5,id1,1,0);add(id2,id1,1,0);add(id3,id5,1,1);add(id4,id2,1,1);maxx+=2;}
            if(a[i][j]==12){add(id4,id1,1,0);add(id5,id1,1,0);add(id2,id4,1,1);add(id3,id5,1,1);maxx+=2;}
            if(a[i][j]==7){add(id2,id1,1,0);add(id3,id1,1,0);add(id4,id1,1,0);add(id5,id2,1,1);add(id5,id3,1,2);add(id5,id4,1,1);maxx+=3;}
            if(a[i][j]==11){add(id2,id1,1,0);add(id3,id1,1,0);add(id5,id1,1,0);add(id4,id2,1,2);add(id4,id3,1,1);add(id4,id5,1,1);maxx+=3;}
            if(a[i][j]==13){add(id2,id1,1,0);add(id4,id1,1,0);add(id5,id1,1,0);add(id3,id2,1,1);add(id3,id4,1,1);add(id3,id5,1,2);maxx+=3;}
            if(a[i][j]==14){add(id3,id1,1,0);add(id4,id1,1,0);add(id5,id1,1,0);add(id2,id3,1,1);add(id2,id4,1,2);add(id2,id5,1,1);maxx+=3;} 
            if(a[i][j]==5){add(id2,id1,1,0);add(id4,id1,1,0);maxx+=2;}
            if(a[i][j]==10){add(id3,id1,1,0);add(id5,id1,1,0);maxx+=2;}
            if(a[i][j]==15){add(id2,id1,1,0);add(id3,id1,1,0);add(id4,id1,1,0);add(id5,id1,1,0);maxx+=4;}
        }
    }
    for(re int i=1;i<=n;i++)
        for(re int j=1;j<=m;j++){
            if((i+j)%2==0){
                if(i>1) add(id2,id4-m,1,0);
                if(j>1) add(id5,id3-1,1,0);
                if(i<n) add(id4,id2+m,1,0);
                if(j<m) add(id3,id5+1,1,0);
            }
        }
    int d=max_flow();
    cout<<((maxx==d<<1)?tot:-1);
    return 0;
}

4.[HAOI2017]新型城市化

題目描述

Anihc國有n座城市.城市之間存在若一些貿易合做關係.若是城市x與城市y之間存在貿易協定.那麼城市文和城市y則是一對貿易伙伴(注意:(x,y)和(y,x))是同一對城市)。

爲了實現新型城市化.實現統籌城鄉一體化以及發揮城市羣輻射與帶動做用.國 決定規劃新型城市關係。一些城市可以被稱爲城市羣的條件是:這些城市兩兩都是貿易伙伴。 因爲Anihc國以前也一直很重視城市關係建設.因此能夠保證在目前已存在的貿易合做關係的狀況下Anihc的n座城市能夠剛好被劃分爲不超過兩個城市羣。

爲了建設新型城市關係Anihc國想要選出兩個以前並非貿易伙伴的城市.使這兩個城市成爲貿易伙伴.而且要求在這兩個城市成爲貿易伙伴以後.最大城市羣的大小至少比他們成爲貿易伙伴以前的最大城市羣的大小增長1。

Anihc國須要在下一次會議上討論擴大建設新型城市關係的問題.因此要請你求出在哪些城市之間創建貿易伙伴關係能夠使得這個條件成立.即創建此關係先後的最大城市羣的 大小至少相差1。

輸入輸出格式

輸入格式:

第一行2個整數n,m.表示城市的個數,目前尚未創建貿易伙伴關係的城市的對數。

接下來m行,每行2個整數x,y表示城市x,y之間目前尚未創建貿易伙伴關係。

輸出格式:

第一行yi個整數ans,表示符合條件的城市的對數.注意(x,y)與(y,x)算同一對城市。

接下來Ans行,每行兩個整數,表示一對能夠選擇來創建貿易伙伴關係的城市。對於 一對城市x,y請先輸出編號更小的那一個。最後城市對與城市對之間順序請按照字典序從小到大輸出。

輸入輸出樣例

輸入樣例#1:

5 3
1 5
2 4
2 5

輸出樣例#1:

2
1 5
2 4

說明

數據規模與約定

數據點1: n≤16

數據點2: n≤16

數據點3~5: n≤100

數據點6: n≤500

數據點7~10: n≤10000

對於全部的數據保證:n <= 10000,0 <= m <= min (150000,n(n-1)/2).保證輸入的城市關係中不會出現(x,x)這樣的關係.同一對城市也不會出現兩次(無重邊.無自環)。

Solution

既然把題放在這裏了,那麼天然就不會像題解裏寫的solution同樣了。

其實這個題的主體思路在前面的幾個分塊中沒有來的及體現出來,涉及到二分圖的問題。這也是筆者一個失誤,二分圖類的問題必定近期補到前面去。

那麼咱們繼續來看這道題。題目給出的是一些城市之間的關係。而後看那個城市羣的定義,這些城市兩兩之間可以互相到達。加上給出的城市之間的關係是之間沒有路徑可以互相到達,那麼就比較明顯了,若是咱們根據題目中要求把城市連起來,那麼咱們必定能夠獲得一個二分圖。(爲何忽略了只能是一個城市羣的狀況??由於就一個城市羣你也沒有能夠創建的關係了,這麼出題沒有意義。)

而後咱們仔細考慮這個題讓咱們幹什麼,在兩個原本沒有關係的城市之間創建關係,就是從咱們剛剛創建的二分圖裏面刪去一條邊,最大城市羣的大小增長1,就是讓咱們這個二分圖裏的最大獨立集增長1。又由於定理:最大獨立集=總點數-最小覆蓋集=最大匹配。因此咱們要求的就是刪去一條邊能夠使二分圖的最大匹配減少1的總邊數。而後咱們能夠用網絡流來實現。

那麼咱們如今就有了一個初步的作法了,大致思路就是枚舉全部的邊,而後不停的網絡流,以後慢慢的計算有那些邊能夠刪掉。不過這樣過5個點也就差很少快超時了,網絡上有大佬說退流或許可以達到更好的時間效率,不過我看上去並無從根本上優化算法,因此過掉第6個點仍是懸。那麼咱們有沒有更優化的方法呢?

有的,根據定理:若一條邊必定在最大匹配中,則在最終的殘量網絡中,這條邊必定滿流,且這條邊的兩個頂點必定不在同一個強連通份量中。 可得,咱們只須要經過殘量網絡跑一遍Tarjan就能夠了。那麼這個定理又是怎麼來的呢。。(話說爲何有這麼多的定理。)

筆者淺顯的證實一下:首先要滿流,而後不在同一個強連通份量裏是指,若是在同一個強連通份量裏,那麼咱們在這個強連通份量內部增廣一下,整個份量裏的殘量都會變化,可是網絡的最大匹配並不會變化,也就是說咱們又能獲得一個新的最大匹配,也就是說這條邊的存在是無可厚非的。

那麼能夠放上代碼了。

Code

#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <iostream>
#include <cstdlib>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <queue>
#include <set>
#include <map>
#define MAXN 200000
#define re register
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define ms(arr) memset(arr, 0, sizeof(arr))
const int inf = 0x3f3f3f3f;
struct po
{
    int nxt,to,w,from;    
}edge[250001];
struct ANS
{
    int x,y;
}ans[MAXN];
int n,m,cur[MAXN],head[20002],num=-1,dep[20002],s,t,c[MAXN],vis[20002];
inline int read()
{
    int x=0,c=1;
    char ch=' ';
    while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
    while(ch=='-') c*=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-'0',ch=getchar();
    return x*c;
}
inline void add_edge(int from,int to,int w)
{
    edge[++num].nxt=head[from];
    edge[num].from=from;
    edge[num].to=to;
    edge[num].w=w;
    head[from]=num;
}
inline void add(int from,int to,int w)
{
    add_edge(from,to,w);
    add_edge(to,from,0);
}
inline bool bfs()
{
    memset(dep,0,sizeof(dep));
    queue<int> q;
    while(!q.empty())
    q.pop();
    q.push(s);
    dep[s]=1;
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        for(re int i=head[u];i!=-1;i=edge[i].nxt)
        {
            int v=edge[i].to;
            if(dep[v]==0&&edge[i].w>0)
            {
                dep[v]=dep[u]+1;
                if(v==t)
                return 1;
                q.push(v);
            }
        }
    }
    return 0;
}
inline int dfs(int u,int dis)
{
    if(u==t)
    return dis;
    int diss=0;
    for(re int& i=cur[u];i!=-1;i=edge[i].nxt)
    {
        int v=edge[i].to;
        if(edge[i].w!=0&&dep[v]==dep[u]+1)
        {
            int check=dfs(v,min(dis,edge[i].w));
            if(check!=0)
            {
                dis-=check;
                diss+=check;
                edge[i].w-=check;
                edge[i^1].w+=check;
                if(dis==0) break;
            }
        }
    }
    return diss;
}
inline void dinic()
{
    while(bfs())
    {
        for(re int i=s;i<=t;i++)
        cur[i]=head[i];
        while(int d=dfs(s,inf));
    }
}
void put_color(int u,int col)
{
    c[u]=col;
    vis[u]=1;
    for(re int i=head[u];i!=-1;i=edge[i].nxt){
        int v=edge[i].to;
        if(!vis[v]) put_color(v,col^1);
    }
}
int dfn[MAXN],low[MAXN],stack[MAXN],color_num,color[MAXN],cnt,top;
inline void Tarjan(int u)
{
    dfn[u]=low[u]=++cnt;
    vis[u]=1;
    stack[++top]=u;
    for(re int i=head[u];i!=-1;i=edge[i].nxt){
        if(!edge[i].w){
            int v=edge[i].to;
            if(!dfn[v]){
                Tarjan(v);
                low[u]=min(low[u],low[v]);
            } else if(vis[v]) low[u]=min(low[u],dfn[v]);
        }
    }
    if(dfn[u]==low[u]){
        color[u]=++color_num;
        vis[u]=0;
        while(stack[top]!=u){
            color[stack[top]]=color_num;
            vis[stack[top--]]=0;
        }
        top--;
    }
}
int x[MAXN],y[MAXN],tot;
inline bool cmp(ANS a,ANS b){
    return a.x==b.x?a.y<b.y:a.x<b.x;
}
int main() 
{
    memset(head,-1,sizeof(head));
    n=read();m=read();
    for(re int i=1;i<=m;i++){
        x[i]=read();y[i]=read();
        add_edge(x[i],y[i],0);
        add_edge(y[i],x[i],1);
    }
    
    for(re int i=1;i<=n;i++)
        if(!vis[i]) put_color(i,2);
    memset(head,-1,sizeof(head));
    s=0,t=n+1;num=-1;
    for(re int i=1;i<=n;i++){
        if(c[i]==2)
            add(s,i,1);
        else add(i,t,1);
    }
    for(re int i=1;i<=m;i++){
        if(c[x[i]]==2)
            add(x[i],y[i],1);
        else add(y[i],x[i],1);
    }
    dinic();
    memset(vis,0,sizeof(0));
    for(re int i=1;i<=n;i++)
        if(!dfn[i]) Tarjan(i);
    for(re int i=0;i<=num;i+=2){
        int u=edge[i].from,v=edge[i].to;
        if(!edge[i].w&&color[u]!=color[v]&&u!=s&&v!=t&&u!=t&&v!=s){
            if(u>v) swap(u,v);
            ans[++tot].x=u;ans[tot].y=v;
        }
    }
    
    sort(ans+1,ans+tot+1,cmp);
    cout<<tot<<endl;
    for(re int i=1;i<=tot;i++)
        printf("%d %d\n", ans[i].x,ans[i].y);
    return 0;
}
/*6 5 3 7 2 4 1*/

待續。。。

相關文章
相關標籤/搜索