網路流模板 網路流題型大薈萃
阿新 • • 發佈:2018-12-23
以HDU4560為例,整理了很多網路流的題目——
#include<stdio.h> #include<iostream> #include<string.h> #include<ctype.h> #include<math.h> #include<map> #include<set> #include<vector> #include<queue> #include<string> #include<algorithm> #include<time.h> #include<bitset> using namespace std; void fre() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); } #define MS(x,y) memset(x,y,sizeof(x)) #define MC(x,y) memcpy(x,y,sizeof(x)) #define MP(x,y) make_pair(x,y) #define ls o<<1 #define rs o<<1|1 typedef long long LL; typedef unsigned long long UL; typedef unsigned int UI; template <class T> inline void gmax(T &a, T b) { if (b>a)a = b; } template <class T> inline void gmin(T &a, T b) { if (b<a)a = b; } const int N = 240, M = 14000, Z = 1e9 + 7, inf = 0x3f3f3f3f; int casenum, casei; int n, m, g, L; bool e[80][80]; /* 【演算法介紹】 網路流常用於解決分配匹配等問題。 其主要演算法包括dinic和sap(isap) 其中,Dinic是基於層次圖的網路流模型,時間複雜度為O(n ^ 2 * m) 【演算法實現】 1,先通過bfs,在有流量的條件下,找到從超級源點ST到超級匯點ED的最短路 2,再通過dfs,在確保是最短路的條件下,找到一條可行的增廣路 3,重複過程(1,2),直到過程1無法找到可行路徑 注意: 1,網路流的實現,必然要依賴反圖和反邊,id = 1的初始化莫不可忘記。 2,每條單向邊都對應著正邊和反邊,整個圖的最大邊數是所有單向邊的數量 * 2 3,我們初始化的時候,一定要使得所有first都清零,而不只是1 ~ n的first清零 4,建圖是可以慢慢化簡的,如果想不到好的建圖方式,可以先想拙劣建圖法再分析化簡 【最小割】 在網路流模型中,最大流 = 最小割。 所謂最小割, 是指用最小的成本,使得源點和匯點不連通。 最小割模型有兩種可能的答案生成方式—— 1,最大流就是答案 2,所有的正權,減去最大流才是答案 我們可以對對最小割生成方案,用最小割解決一些模型,或者利用最小割的思想解決問題。 【最大權閉合子圖】 所謂最大權閉合子圖,大概有這樣的問題結構—— 1,告訴你每個點的權值(權值有正有負) 2,點與點之間的選取,存在一定的依賴關係。 就比如說如果選了x,則必須要選擇另外一些點{p1, p2, p3},即{px}是x的必要條件。 3,你需要決定選出若干點,進而使得點權之和最大 建圖方式: 1,源點->正權點,容量為權值絕對值 2,負權點->匯點,容量為權值絕對值 3,對於如果選了x,就必須要選擇y的情況,我們從x向y建立一條容量為inf的邊 在這種情況下,我們跑最大流,得到最小割 與ST相連的點屬於最大權閉合子圖中的點,與ED相連的點不屬於最大權閉合子圖。 理解: 最大流 = 最小割,而最小割,意味著以最小成本,使得源點與匯點不連通。 在這種建圖方式下—— 割掉ST->正權點的邊,意味著記下來所對應的成本太大了,我們不如放棄該正權 割掉負權點->ED的邊,意味著我們雖然使用了這些成本,但是正權還沒有被畫完 而依賴關係之前的正無窮的邊, 意味著這前驅節點和後繼節點之間是不可同時割捨的 總的正權值 - 最小割, 就是問題的解 【混合圖歐拉回路】 網路流可以解決混合圖求歐拉回路的問題 混合圖歐拉回路問題大概是這樣:告訴你一個圖,有些邊方向是確定的,有些邊方向是不確定的(需要你定向)。 要求你在定向之後,使得該圖變為尤拉圖。 首先我們把雙向邊任意定向,並對於每個點,統計其在當前定向條件下的入度和出度。 如果對於任意點,(入度 - 出度) % 2 == 1,則無解。 否則我們設(ind[i] - oud[i]) / 2 = w 對於入度>出度的點(即w > 0),我們從這個點向ED連容量為abs(w)的邊使之平衡化 對於出度>入度的點(即w < 0),我們從ST向這個點連容量為abs(w)的邊使之平衡化 然後跑最大流。如果最大流為∑{w, w > 0},則表示問題有解。 理解: 對於入度 > 出度的點,其向ED連了容量為abs(w)的邊都要流滿 如果流滿了,流出的容量為出度+w = 入度-w,也就是說,我們改變1/2的入度的方向即可 對於入度 < 出度的點,其從ST連了容量為abs(w)的邊都要流滿 如果流滿了,流入的容量為入度+w = 出度-w,也就是說,我們改變1/2的出度的方向即可 於是這種建圖能對應得到問題的解。 如果要輸出方案,我們把所有從x到y沒有流量的邊反向即可 該邊流滿,說明我們選擇了這條邊;該邊未滿,說明這條邊需要反向 */ int ST, ED; int first[N], ID; struct Edge { int w, cap, nxt; }edge[M]; void ins(int x, int y, int cap_) { edge[++ID] = { y, cap_, first[x] }; first[x] = ID; edge[++ID] = { x, 0, first[y] }; first[y] = ID; } int d[N]; bool bfs() { MS(d, -1); queue<int>q; q.push(ST); d[ST] = 0; while (!q.empty()) { int x = q.front(); q.pop(); for (int z = first[x]; z; z = edge[z].nxt)if (edge[z].cap) { int y = edge[z].w; if (d[y] == -1) { d[y] = d[x] + 1; q.push(y); if (y == ED)return 1; } } } return 0; } int dfs(int x, int all) { if (x == ED)return all; int use = 0; for (int z = first[x]; z; z = edge[z].nxt)if (edge[z].cap) { int y = edge[z].w; if (d[y] == d[x] + 1) { int tmp = dfs(y, min(edge[z].cap, all - use)); edge[z].cap -= tmp; edge[z ^ 1].cap += tmp; use += tmp; if (use == all)break; } } if (use == 0)d[x] = -1; return use; } int dinic() { int ret = 0; while (bfs())ret += dfs(ST, inf); return ret; } void build(int mid) { MS(first, 0); ID = 1; for (int i = 1; i <= m; i++)ins(n + i, n + m + i, L); for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (e[i][j])ins(i, n + m + j, 1); else ins(i, n + j, 1); } } ST = 0; ED = n + m + m + 1; for (int i = 1; i <= n; i++)ins(ST, i, mid); for (int i = 1; i <= m; i++)ins(n + m + i, ED, mid); } void debug() { for (int x = 0; x <= ED; ++x) { for (int z = first[x]; z; z = nxt[z])if (z % 2 == 0)//正邊 { int y = w[z]; if(cap[z ^ 1])printf("%d->%d(%d)\n", x, y, cap[z ^ 1]); }puts(""); } } int main() { scanf("%d", &casenum); for (casei = 1; casei <= casenum; casei++) { scanf("%d%d%d%d", &n, &m, &g, &L); MS(e, 0); while (g--) { int x, y; scanf("%d%d", &x, &y); e[x][y] = 1; } int l = 0; int r = m; while (l < r) { int mid = (l + r + 1) >> 1; build(mid); if (dinic() == mid * n)l = mid; else r = mid - 1; } printf("Case %d: %d\n", casei, l); } return 0; } /* [題意] 本題為HDU4560 n個歌手,m種歌曲流派(n<=m<=75) 我們想要安排儘可能多的演唱會。不過有以下條件—— 1,每場演唱會中,每個歌手要唱不同型別的歌曲。 2,這樣可能導致有些歌手去唱他不擅長的歌曲。對於任一種歌曲,被不合適唱的次數都不能超過L。 問你最多能安排多少場演唱會。 [做法] 首先我們把煩瑣的問題資訊提取出來。 本題中出現了三類資訊——歌手、歌曲、演唱會 我們沒辦法通過流量關係,使得一個演唱會在收集了n個不同的歌曲之後,向匯點貢獻1的收益。 我們研究一下性質,發現,如果我們二分了演唱會的場次(顯然滿足單調性,可以二分) 我們只要使得每首歌被最多演唱該場次次就好了,即我們把演唱會由點轉化為了二分的容量上限。 然而,對於每個歌曲而言,其有擅長演奏和不擅長演奏兩種關係,被不擅長演奏的次數不能超過L 這裡考慮把第i首歌拆點為i和m+i,之間的容量上限為L,i表示不擅長的,m+i表示擅長的 然後每個歌手向其擅長或不擅長的點連容量上限為1的邊。 這樣跑最大流,如果最大流 = 歌手數 * 演唱會數,那說明二分成功。由此得到答案 POJ1149 [題意] 有 M 個豬圈,每個豬圈裡初始時有若干頭豬。一開始所有豬圈都是關閉的。 依次來了 N 個顧客,每個顧客分別會開啟指定的幾個豬圈,從中買若干頭豬。 每個顧客分別都有他能夠買的數量的上限。 每個顧客走後,他開啟的那些豬圈中的豬,都可以被任意地調換到其它開著的豬圈裡,然後所有豬圈重新關上。 問總共最多能賣出多少頭豬。( 1 <= N <= 100, 1 <= M <= 1000) 舉個例子來說。有 3 個豬圈,初始時分別有 3、 1 和 10 頭豬。 依次來了 3 個顧客,第一個開啟 1 號和 2 號豬圈,最多買 2 頭; 第二個開啟 1 號和 3 號豬圈,最多買3 頭; 第三個開啟 2 號豬圈,最多買 6 頭。 那麼,最好的可能性之一就是第一個顧客從 1 號圈買 2 頭,然後把 1 號圈剩下的 1 頭放到 2 號圈; 第二個顧客從 3號圈買 3 頭; 第三個顧客從 2 號圈買 2 頭。總共賣出 2+3+2=7 頭。 [分析] 有 M 個豬圈,每個豬圈裡初始時有若干頭豬。一開始所有豬圈都是關閉的。 依次來了 N 個顧客,每個顧客分別會開啟指定的幾個豬圈,從中買若干頭豬。 每個顧客分別都有他能夠買的數量的上限。 每個顧客走後,他開啟的那些豬圈中的豬,都可以被任意地調換到其它開著的豬圈裡,然後所有豬圈重新關上。 問總共最多能賣出多少頭豬。( 1 <= N <= 100, 1 <= M <= 1000) 舉個例子來說。有 3 個豬圈,初始時分別有 3、 1 和 10 頭豬。 依次來了 3 個顧客,第一個開啟 1 號和 2 號豬圈,最多買 2 頭; 第二個開啟 1 號和 3 號豬圈,最多買3 頭; 第三個開啟 2 號豬圈,最多買 6 頭。 那麼,最好的可能性之一就是第一個顧客從 1 號圈買 2 頭,然後把 1 號圈剩下的 1 頭放到 2 號圈; 第二個顧客從 3號圈買 3 頭; 第三個顧客從 2 號圈買 2 頭。總共賣出 2+3+2=7 頭。 2016計蒜之道複賽F [題意] https://nanti.jisuanke.com/t/11215 n(100)個點和m(C(n,2))條雙向邊 有詢問st,md,ed(三點各不相同) 問你是否存在一條合法路徑,使得我們從st到md,再從md到ed,同一個點最多隻能經過一次。 [分析] 我們以md為源點建圖,在st和ed處都各自向匯點連一條容量為1的邊,看最大流是否為2 但是這裡因為每個點最多隻能經過一次,所以我們需要做拆點處理 然而因為最多隻增廣兩次,雖然理論複雜度是O(nnm),但是實際複雜度卻只有O(n+m) hihocoder1389 ACM-ICPC國際大學生程式設計競賽北京賽區(2016)網路賽 G [題意] 有n(100)個工廠,會製造垃圾。 有m(100)個垃圾處理廠,可以處理垃圾 每個工廠有屬性(縱座標,橫座標,日排放垃圾量) 每個垃圾處理廠有屬性(縱座標,橫座標) 對於所有垃圾處理廠,其可以設定它的處理能力w和處理半徑d(統一設定) 成本就是w*sqrt(d),讓你用最小的成本處理完所有工廠的垃圾 [分析] 這道題有兩個引數需要我們設定,處理半徑d和處理能力w 對於處理半徑,並沒有什麼單調性 但是處理半徑最多也不過有n*m種可能 於是我們可以處理出所有的處理半徑,並做去重(優化1,去重), 然後按照半徑從大到小的順序,列舉所有可行的處理半徑(優化2:該處理半徑必要能把所有工廠都覆蓋) 在確定了處理半徑之後, 我們二分每個垃圾處理廠的處理能力,其二分下界顯然是Basic, 而二分上界則通過之前答案做最優性剪枝(即min(TOP, (int)(ans / sqrt(sqrt(d) + 1)));) 每次跑dinic來驗證該引數設定的合法性 具體的建圖方式很簡單—— 源點->工廠(垃圾生產量) 工廠->能處理其的垃圾處理廠(無窮大 or whatever) 垃圾處理廠->匯點(工廠處理能力) HDU3572 [題意] 有m(200)個機器,n(500)個任務 每臺機器每天只能進行一項任務,但是每個任務可以在不同的天打斷或者在不同的機器上執行 每個任務有開始時間,持續時間,結束時間,時間範圍不超過500 現在詢問是否存在一個可行的方案,使得按時完成所有任務。 [分析] 有三類資訊——機器、任務、時間 源點向每個任務連容量為其完成需要時間的邊 每個任務向其所在的時間段內的每天連容量為1的邊 每個時間向各個機器都連容量為1的邊,每臺機器向匯點連容量為天數(或者極大值)的邊 但是這裡我們可以優化一下——每個時間向匯點連容量為機器數的邊,然後跑最大流 HDU2883 [題意] n(200)個人來買蛋糕 商店同時可以做m(1000)個蛋糕 對於第i個人,要求蛋糕最早在si做,最晚在ei做(時間範圍為[1,1e6]), 需要買ni(50)塊蛋糕,每塊蛋糕要求烹飪ti(50)時間 問你是否可以滿足所有人的需要 [分析] 這道題與HDU3572十分類似,ni完全可以*=ti表示第i個人所需要的時間 然而差異是,我們沒辦法把時間點逐一抽象為具體點 但是我們發現時間區間段最多不過400段,於是我們可以對時間區間段做離散化 超級源點向人連邊,容量為人需要做蛋糕的總時間 人向時間區間段連邊,容量為在這個區間段能夠做這些蛋糕的時間,即(a[j+1]-a[j])*g[i] (這裡應該用到蛋糕的數量,使得同一個蛋糕不能同時在多個機器上烤。但是本題的設計卻要忽略這個) 從時間區間段向超級匯點連邊,流量為時間長度*m HDU2732 [題意] 有一個n*m的矩陣,每個點是一根柱子,每根柱子有一個最大的承受跳躍的次數,就是允許蜥蜴跳上去的次數 所有蜥蜴最大跳躍的歐幾里得距離告訴你 所有蜥蜴所在的位置也告訴你 所有柱子的資訊也告訴你 問你是否存在一種方案,使得所有的蜥蜴都跳出這個矩陣,注意同一時間同一根柱子上最多隻能有一隻蜥蜴 [分析] 建圖方式—— 超級源點->有蜥蜴的柱子(1) 每根柱子拆點,入->出(承重次數) 出->其他能跳到的柱子or終點(inf) HDU3338 [題意] 給定一個n*m的方格, 這些格子有點格子是需要填數的,另外一些格子有記錄關於行或列的值。 我們需要填數,使得所有記錄值都滿足。 [分析] 這裡要要求每個點的流量需要是在[1~9]之間,即流量產生了下界。 這時上下界網路流的問題,我們可以通過上下界網路流的方法解決。 當然還有其他方法,我們把下界 -= 1,使得流量消除掉下界的影響。 這裡既要行滿足又要列滿足,於是我們按照—— 列 -> 點 -> 行的方式建邊,檢視能否跑出正確的最大流即可 Codeforces Round #304 (Div. 2)E [題意] http://codeforces.com/contest/546/problem/C 有n(100)個點m(200)條邊 一開始n個點上分別有a[i]個人 我們希望最後n個點上分別有b[i]個人 每個人可以不動或者最多通過一條邊走一次 [分析] 這道題我一開始想到的是費用流—— 拆點是很必要的,每個點拆成兩個點x, y。 源點流向x,流量為a[i],費用為0, x流向自己的y和其他點的y,流量為無限大,費用為1, y流向匯點,流量為b[i],費用為0, 如果最大流 = n且費用 = n即可以。 然而這道題我們還可以通過最大流解決—— 因為這裡費用為1的條件實際上是並沒有什麼卵用的。 HDU5352 [題意] 有n個城市,m個時間點,再給定一個K(n<=200,m<=500,k<=200) 這n個城市一開始都是廢墟 對於每個時間點有3個操作 1 x:自己選擇重建若干個城市(可以為0~K的任一值),這些城市必須與x相連通(包括x) 2 x y:在x和y之間建一條雙向邊。 3 p (x1 y1)(x2 y2)(...)(xp yp):刪掉這p條邊。 我們希望在m個時間點結束之後,重建城市的數量儘可能多。 在滿足重建城市儘可能多的基礎上,我們希望使得{每個時間點修建城市數}這個序列的字典序儘可能小。 [分析] 首先,這是關於分配的問題—— 關於分配的問題,我們想到用網路流來求解 我們是把時間點與城市之間分配 所以,建圖就是—— 超級源點向所有時間點連邊,每個時間點向其所可以建的城市連邊。 然後,如果不考慮字典序的話,這道題已經結束了,跑最大流就是答案 可以如果考慮字典序的話,我們就要使得倒著分配到儘可能多的的城市,即倒著跑最大流 靠後的時間點分配的城市多,那麼靠前的時間點分配的城市自然就少了 這個是滿足網路流的參量網路增廣思想,分配可能會變,但最大流量是不會變的。 因為已經有時間點了,所以我們我們並不需要記錄所有詢問再倒著處理。 只需要在倒序處理的時候,在所有操作1的位置,使得源點多增加K條邊的容量上限就好啦 2015HDU Summer Trainning(4) - Team 10J [題意] 給你一個n * m的棋盤(n和m都在40的範圍內) 每次選擇兩個相鄰的格子,兩個數都+1,問最少多少次操作可以使得棋盤上的所有數都變成同一個數。如果無法實現輸出-1 [分析] 這道題的點權很大,但是點數並不多。 我們發現這題有奇偶點的性質,每次是奇點和偶點權值共同+1 ★這個性質十分重要★ 1,如果格子數為偶數, 最終偶點的權值和與奇點的權值和是相同的,所以要求初始先統計出奇點的權值和和偶點的權值和,兩者必須相同,否則直接判-1 我們有操作的最小次數(設為bot),顯然也會有多一些的次數。 我們發現,多的次數是可以在小次數的基礎上發展而來的。 那麼達成bot,bot+1,bot+2,……,都是可行的,滿足單調性,可以二分。 2,如果格子數為奇數(即偶點比奇點多1),結合★的性質,我們發現合法的目標是唯一的。 問題一——為什麼呢? 因為在bot的基礎上,所有點都擡升v,顯然偶點權值和會比奇點權值和多v,肯定無法達成。 問題二——如何計算呢? 設目標權值是x, 設偶點的個數為num0,偶點初始的權值和為v0 設奇點的個數為num1,奇點初始的權值和為v1 我們可以算得偶點一共加了多少次,奇點一共加了多少次,兩者必須相同。 那麼所有偶點的權值增加量為num0*x-v0 所有奇點的權值增加量為num1*x-v1 必然有num0*x-v0=num1*x-v1 只有x是未知量,x=(v0-v1)/(num0-num1),即x=v0-v1; 這樣設定一個超級源點,向所有偶點連邊,容量為目標權值-初始權值 設定一個超級匯點,所有奇點向超級匯點連邊,容量為目標權值-初始權值。 所有奇點向周圍的所有偶點連邊,容量為無窮大。 這樣跑一個最大流,如果最後,最大流=∑每個點的目標權值-初始權值,這個目標權值就是合法的。 Codeforces Round 290 (Div 2)E [題意] 有n個數,每個數的範圍都在[2,10000]之間。 我們希望把這些數安排在若干個環上, 使得每個環,數的個數都>=3,且所有相鄰的數之和都為素數。 [分析] 這題有很多地方是可以深入分析的—— 1,首先兩個數的和是質數,每個數都是[2,10000]範圍內的數,所以和為素數的2個數必定是一奇一偶 2,然後因為最後所成是圓桌,所以點數必定為偶數, 3,每桌人數至少為3,這個限定有什麼意義呢?只是斷絕了2個人一桌的可能性。 也就是說,兩個人的相鄰關係,最多隻有1個,不能double化。 奇偶性就已經讓我們想到網路流了。 而不能double化也向我們發現這個恰好契合於流量的性質。 於是我們可以—— 把所有奇點向"與它之和為素數的"偶點連邊,方向是奇->偶,容量是1, 然後所有奇點從超級源點收入容量為2的邊 所有偶點至超級匯點流出容量為2的邊 然後跑最大流。 因為題目要求其實就是—— 1,每個奇點恰好與2個偶點匹配, 2,每個偶點恰好與2個奇點匹配。 所以如果最後的流量恰好為n,就是有可行方案的。 HDU5594 BestCoder Round 65E n個數形成若干至少3元素素數環的可行性檢驗 數可以為1 [題意] 這道題是 CF290(Div2)E的升級版。 不同的是,這道題的數值範圍包括了1. 如果不包括1,問題會怎麼樣? 顯然,素數不可能是2,只會是奇數。 於是,相鄰兩個數必定是一奇一偶。 於是,每個環中數的個數也必定為偶數個。 (題目中也必須要使得奇數和偶數的個數都相同) 然後,我們把所有奇數向與之之和為素數的偶數之間連一條流量為1的邊 超級源點向所有奇數連一條流量為2的邊 所有奇數向超級匯點連一條流量為2的邊 然後如果最大流==點數n,那麼說明有解。 這樣建圖,每個奇數必然會匹配到2個偶數, 於是自然使得每個組中,數的個數都必然至少為4個,保證了做法天衣無縫。 =============================================================== 問題迴歸,現在數可以為1了。 會產生什麼影響呢? 如果只有1個1,是沒有任何影響的。 如果超過1個1,兩個1之間是可以形成一個素數的,即,會出現奇數向奇數連邊的情況。 而且,其實對我們產生干擾的,也只有1向1連邊這一種情況。 (情況1)如果這些1不形成環,只是鏈關係的話, 我們可以考慮移除1~n-1個1,設剩餘數的個數為m,我們需要檢視,是否剩下的數可以跑出流量為m的最大流。 至於我麼移除的1,隨便和剩下的哪個1相鄰即可。(也就是有一種——1個1可以拆出多個1的意味。) 這個肯定不會把是YES的情況判定為NO。這種做法下,1的個數需要至少為2個。 (情況2)然而,如果1的個數如果是大於等於3,我們是可以把所有的1都移除的。因為這些1可以自成一環。 以上做法的思想是什麼呢? 就是因為發現1可以與自己相連。所以採取的一種—— "如果1多了,以至於出現1和1連邊的情況了,那麼我們試著減少1的數量"的做法。 (情況3)然而,這個量的減少,有時候會產生錯誤—— 因為1的減少,我們可能一個環中只剩下2個數了。 如果有這麼的一個情況出現,這種環的個數,也必然只有1個。否則2個環可以拼起來。 於是,我們縮1,不能盲目縮,我們可以縮出special one。 什麼叫做special one呢? 就是這個1,連向的sum=prime的偶數的邊的流量,由1變成了2。 這個解決了情況3出現的影響。也不會產生任何非法干擾。於是就可以AC掉這題啦! 這題我會不斷刪點,不斷做網路流。 然而流量是不斷減少的,於是我們每次都要重置邊的流量。 所以,我們可以使得一開始從流量最少的情況入手,不斷加邊。這樣就不會重置邊,可以有加速的奇效。 然而,我們還發現,在經過我們的縮1操作處理之後。 實際上,奇數的個數還是要和偶數個數保持相同。 因為偶數個數是不變的。所以,事實上只需要對一個值跑網路流即可。 啦啦啦啦啦~ Codeforces Round 284 (Div 2)E [題意] 有n(100)個數a[] 有m(100)個pair,每個pair的兩個數必然一奇一偶 我們每次操作可以選擇一個pair,和一個值v,要求v是這個兩個數的公約數,使得這個pair的兩個數都/=v 問你最多的操作次數。 題目保證pair兩兩不同。 [分析] 首先,這道題設計到奇偶性質,我們很自然就想到了網路流。 然後,基於貪心原則,我們發現v必然一定取素數。 素數不同條件下的建邊不能相互影響,於是我們可以拆素因子, 即奇數x->奇數x的素因子p1(num1)->與x可以匹配的偶數y的素因子p1->x可以連邊的偶數y(numy) ->奇數x的素因子p2(num2)->與x可以匹配的偶數z的素因子p2->可以連邊的偶數z(numz) 這種限制可以使得對於每個數,素因子的統計不會過量,且使得匹配關係可以對應實現 不過因為不同素因子之間相互獨立,所以我們還可以列舉素因子,每次列舉素因子之後單獨跑一次dinic—— 1,對於每個奇數,數中含有幾個v,就從超級源點向其建立容量為幾的邊 2,對於每個偶數,數中含有幾個v,就從其向超級匯點建立容量為幾的邊 3,對於一個(奇數,偶數)pair,建一條邊,容量為它們共有的v的數量。 求和就是答案 IndiaHacks 2016 - Online Edition (Div 1 + Div 2) ErrichtoD Delivery Bears [題意] n(50)個點,m(500)條有向邊,我們有g(1e5)只運輸熊。 每隻熊都必須運輸同樣重要的貨物(重量可以為double),從1點到n點。 問你每隻熊最多可以運多少的貨物 [分析] 如何把我們轉化成——每隻熊都運同樣重量的貨物呢? 我們很快就可以發現,可以二分每隻熊運送的貨物w, 然後對於每條邊,用a[i].z/w來算出這條邊的容量。 然後跑最大流檢測是否>=g 【最小割模型】 重要問題,輸出方案 [題意] 找出流網路G=(V,E)的一組最小割的邊集 [做法] 1,求出最大流,得到殘量網路 2,從源點開始,順著未滿流的邊做bfs,得到的連通點集表示未與源點割掉的點集 3,列舉所有與源點相連的點x,再列舉這些點的所有邊得到另一側的點y。 如果x與y之間的邊是滿流的邊,則說明這是一條割邊。 HDU5294_2015MUTC第一場7G 基於最短路的最少邊數和最小割 [題意] 給你一個無向圖 從1到n會有一個最短路 問你 (1)最少切除多少條邊, 可以使得最短路變長 (2)最多切除多少條邊, 可以使得最短路依然會這個長度 [分析] 首先肯定要處理出最短路 對於(2),除了最短邊數的最短路的這些邊外,我們把其他邊都刪掉 對於(1),我們是對最短路圖做最小割,在dinic()的bfs和dfs中, 更新的條件都要同時滿足d[y]=d[x]+1&&f[y]=f[x]+c[z] 如果是雙向建邊,我們可能在奇怪的地方連出環會出錯,所以一定不要雙向建圖,最短路是有方向的。 HDU3657 [題意] 給你一個n*m(50*50)的矩陣 每個格子可以選擇 取或不取 如果取,我們會獲得其相應權值 但是如果相鄰的兩個格子xy都同時取,我們會損失2*(x&y)的權值 同時還有一些格子必須取 讓你輸出我們所能夠得到的最大權值 [分析] 這涉及到決策,我們考慮網路流,而這裡是選取問題,我們可以考慮最小割模型。 最小割模型有兩種可能的答案生成方式—— 1,最大流就是答案 2,所有的正權,減去最大流才是答案 這道題按照第二種方式建圖—— 割掉的邊表示我們需要捨棄的成本,因為是最小割,所以是我們以最小的損失使得問題合法化。 1,所有的正權tot = ∑a[][] 2,這個圖其實是二分圖,我們應該想到奇偶建圖方式 從奇點向偶點連邊,邊權為這兩個點同時選時會造成的損失, 3,一個點的選取,並不一定使得這個點與源點相連,如果一個點必須選 奇點的話與源點的邊權為極大值,偶點的話與匯點的邊權為極大值 以這種方式建圖之後—— 如果一個點不選取,這個點會與源點或匯點形成割,也就不用考慮其它問題了。 如果一個點選取,而且其相鄰的點也選取,則我們會計入這份共同選擇的損失割掉。 於是,這個模型下, tot - dinic() 就是答案 HDU4307 [題目] 有一個1*n的矩陣A(我們需要求A),矩陣中的每個元素不是0就是1 給你一個n*n的矩陣B(已知),矩陣中的每個元素都是非負整數 給你一個1*n的矩陣C(已知),矩陣中的每個元素都是非負整數 有這樣的關係方程: D=(A*B-C)*AT(AT是A的轉置矩陣,a[i][j]->a[j][i]) 讓我們(構造這樣的A從而)使得D最大。 [分析] 太神奇了! 我們用最大流可以求解一個函式模型的最小值—— for(int i=1;i<=n;++i) { sum+=x[i]*a[i]; sum+=(1-x[i])*b[i]; for(int j=1;j<=n;++j) { sum+=x[i]*(1-x[j])*c[i][j]; } } 其中x[i]不是0就是1。 意味著,我有n個開關,每個開關控制一個決策不是0就是1. 如果對於開關i:值為1,就會損失a[i]的價值;值為0,可會損失b[i]的價值, 且對於開關對(i,j),i為1且j為0就會損失c[i][j]的價值。 如何對這個模型用最大流建模呢? 我們產生一個假設,假設做完最小割之後—— 值為1的集合是和ST相連的部分,值為0的集合是和ED相連的部分 建圖方式—— ST->點i(i為0的損失量),點i->ED(i為1的損失量) 即這兩條邊的連邊是反著來的 點i->點j(i為1且j為0的損失量) 在這種建圖方式下,我們通過跑最大流的方式得到最小割。 因為是跑最大流,所以這兩條邊必然會至少割掉一條,也就是說,我們確實能夠對每個點作以分配。 要不與ST之間割掉,即i為0的損失小,我們割掉這條與ST相連的邊,表示i的值選取了0 要不與ED之間割掉,即i為1的損失小,我們割掉這條與ED相連的邊,表示i的值選取了1 我們研究任意一條路徑 ST->u->v->ED ST->u的容量為u為0的損失 v->ED的容量為v為1的損失 u->v的容量為u為1v為0的價值 顯然—— 我們要不u選0(割掉ST-u),損失一些價值,就不會再產生u選1其他點選0的損失了 我們要不v選1(割掉v-ED),損失一些價值,就不會再產生u選0其他點選1的損失了 我們要不u選1(不割掉ST-u),v選0(不割掉v-ED),這2者都不割掉。 但是我們就要相應割掉(u->v),因為我們勢必要損失這裡的價值 ========================================================================== 知道了具體函式模型,再回歸該題而言, 我們對D=(A*B-C)*AT做化簡 ans=0; for(int i=1;i<=n;++i) { for(int j=1;j<=n;++j) { ans+=a[i]*a[j]*b[j][i]; } ans-=c[i]*a[i]; } 如果a[i]==1,我們會損失c[i]的收益 如果a[i]==1,a[j]==1,我們會獲得b[j][i]的收益 還不夠,我們再把問題轉化一下—— ans=∑(b[i][j]); <<st for(int i=1;i<=n;++i) { if(a[i] == 1) ans += c[i]; else if(a[i] == 0) ans += ∑b[i][j=1~n] (0的話刪掉整行) if(a[i] == 1 && a[j] == 0)ans += b[i][j] (1的話還刪掉一些同行的格子) } end>> dinic確實是[st end]的最小值,ans-= dinic() 就確實是原始函式的最大值啦 TOJ3864 [題目] http://acm.tju.edu.cn/toj/showp3864.html 有兩個城市 有n(100)個將軍 每一對將軍i和j,如果在同一個城市的話得到lij的殺傷力(l[i][j]=l[j][i]) 但是移動一個將軍i的位置會喪失mi的的殺傷力 現在知道每個將軍初始所在的城市 問你最多能得到多少的殺傷力 [分析] 如果i在1,j在0,損失為l[i][j] 如果i在0,損失為mv*(i==1) 如果i在1,損失為mv*(i==0) 我們想使得總損失儘量小。 建圖—— ST(1)->i(i初始在1,最後去0會損失mv;如果這個損失比較小,我們會選擇割掉另外一條邊,即割掉ST->i的容量為mv) i->ED(0)(i初始在0,最後去1會損失mv,如果這個損失比較小,我們會選擇割掉另外一條邊,即割掉i->ED的容量為mv) i->j(i在1且j在0的損失量l[i][j]) tot-dinic()就是答案 hihocoder1252 2015北京賽區D [題意] 給你一個有向無環圖的技能樹,通常地學習某個技能需要學習全部的前置技能,並且需要一定的花費, 但你可以通過氪金來消掉某個前置關係或者直接強行習得某個技能, 問要學習某個特定的技能需要的最小花費 [分析] 自己對最小割模型的理解還是太差了 建圖方式—— 每個點i拆成兩個點i與i + n,i表示這個技能未學習,i + n表示這個技能已學習 1,如果我們可以通過z的成本,消除x之於y的前置關係,則我們建立從x + n向y建立容量為z的邊 2,如果我們可以通過z的成本,在滿足先決條件下正常習得技能x,則我們建立從ST 向 x建立容量為z的邊 3,如果我們可以通過z的成本,氪金直接習得技能x,則我們建立從x 向 x + n建立容量為z的邊 4,ed + n向ED建立容量為inf的邊 然後跑最大流就是答案 理解: 最大流 = 最小割,這種建圖方式其實是利用了網路流的割模型 然後跑出的最大流,就是指,最小的代價使得ST與ED不連通 因為n + ed 向 ED 的容量為inf,所以顯然我們割掉的肯定不是這條邊 我們可以選擇—— 1,割掉ed -> ed + n,這顯然就是其氪金成本,其他一切條件都無所謂了,就把其前後的連結割掉了 2,割掉preed -> ed,我們要把ed與ed之前的點割掉 顯然我們一定要割掉ST -> ed的邊,也就是說,我們會計入修習這個技能的成本 同時我們可以學習ed之前的點x,也就是說,我們不割掉x + n -> ed,也就是說,我們需要習得x, 至於其怎麼學,無所謂,但是必然要把其與ST的關係斷開。 當然我們也可以不學習ed之前的點x,也就是說,我們割掉x + n -> ed,也就是說,我們消除了前置關係 這樣子其實滿好理解的,最小割就是最大流就是最小學習成本 HDU1565 方格取數 [題意] 有一個n*n的棋盤,每個格子有一個非負數 我們希望取得若干個格子,使得任意兩個格子不相鄰 希望取得數的和最大 [分析] 超級源點向奇數點連邊,流量為點權 奇數點向周圍的點連邊,流量為無限大 偶數點向超級匯點連邊,流量為點權 然後總點權-最大流就是答案。 理解: 這道題求的其實是最大點權獨立集 本題是個二分圖,而二分圖中,最大點權獨立集 + 最小點權覆蓋集 = 點權之和。 二分圖中,覆蓋集的點取走之後,就取走了這個圖裡的所有邊,也就是取得了一個割。 我們求的是最小點權覆蓋集,也就是求最小割,也就是跑最大流。 中國大學生程式設計競賽中南邀請賽(重現)F TC or CF [題意] n(50)場比賽 我們要給每場比賽定個性質,是TC還是CF 但是有限制條件—— 1,第一場必須為TC 2,最後一場必須為CF 3,至少有3場TC 4,至少有3場CF 對於每個人而言,都參加兩場比賽(自己會指明所選的第一場、第二場比賽各是哪一場) 對於第i個人而言,如果他選擇的第一場是TC,第二場是CF,那麼他就會有c[i]的unhappy值產生 問你如何安排,可以使得總的unhappy值最小,並輸出 [分析] 這道題我們是如何做呢?網路流。 我們不確定哪些場次是TC,哪些場次是CF對不對? 我們可以列舉! 這個列舉方式。有2^n爆搜法(其實只要敢寫就能過TwT) 有暴力列舉2場TC和2場CF的n^4法,然後跑網路流,不過會TLE 所以我們想要採取的處理方法是—— 因為每個點不是TC就是CF, 於是我們暴力列舉第2、3、4這3個點(其實列舉2個點就可以,不過沒有列舉3個點快) 這個列舉是2 ^ 3,然後已經確定了一些TC和CF,接著我們再列舉不夠的點即可。 這樣總的列舉複雜度是8 * 2500 之後我們再套上網路流—— 對於網路流,我們建圖方式是這樣的: 超級源點向3個TC,連線容量無窮大的邊 3個CF向超級匯點,連線容量無窮大的邊 然後每個人所對應的第一個點與第二個點之間,連線容量為其unhappy值的邊 這個時候我們再跑最大流,所有列舉方式之後的min{最大流}就是答案 為什麼呢?這個模型本質其實是最小割,最小的成本使得不存在實效性質的TC - CF邊,也就是答案啦 [題意] https://icpcarchive.ecs.baylor.edu/external/68/6887.pdf 有n(10000)個人,m(20000)個關係。 每個關係(x,y)表示第x個人想要第y個人手裡的書——保證x != y即自己不喜歡自己的書,使得沒有自環。 一些人會交換書的條件當且僅當這這些人擁有的書同喜歡的書形成了首尾相連的環 我們想要每個人都拿到一本自己喜歡的書,問能否實現。 [分析] 每個點必須在一個環裡, 就是每個點必須入度為1出度為1且不能有自環。 所以超級源點向每個入點建容量為1的邊, 每個出點向超級匯點建容量為1的邊, 這樣每個點就有一個入一個出,他必然就要在一個環內才會使得最大流為n Intel Code Challenge Final Round (Div 1 + Div 2, Combined) E n個點有產品生產能力和銷售能力C(n,2)條運輸路徑下有最大總銷售量 [題意] 每個點有貨物量p[i]和銷售上限s[i] 對於任意的(i,j)(j>=i),第i個人可以向第j個人運輸最多m件貨物 問你,最好情況下我們可以賣出多少貨物。 [分析] 我們知道,這道題有一個很顯然的最大流解法,就是—— (源點->i,p[i]),(i->匯點,s[i]),(i->j,m),j>i 不過這個複雜度是吃不消的,光邊數就是1e8級別開都開不下。 然而我們應該思考到,有一個常用的轉化就是—— 最大流 = 最小割, 如果我們能求出這個圖使得ST與ED不連通的最小割,問題也就能解啦。 不過如何最小割呢?我們思考一個DP。根據資料規模,我們考慮n^2的DP。 對於每個點而言,這個點要不最終屬於集合ST,要不最終屬於集合ED。 如果這個點i屬於ST,則其需要割掉與ED邊的邊權(即割掉s[i]) 如果這個點i屬於ED,則其需要個點與ST邊的邊權,同時割掉與ST集合中其他點的連邊(即割掉p[i] + numST * m) 也就是說,我們除了列舉當前點i,還需要知道之前在集合ST中選取了多少個點。 於是狀態轉移也就出來啦! dp[i][j]表示我們選取從1開始考慮到了第i個點,在[1~i]中有j個點保持了與ST的連邊不被割掉的當前最小割。 那麼我們有狀態轉移—— 考慮這個點在ED集合:dp[i][j] = dp[i - 1][j] + p[i] + j * m; 考慮這個點在ST集合:dp[i][j] = dp[i - 1][j - 1] + s[i]; 最後的答案是min(f[n][0~n] 【最大權閉合子圖】 所謂最大權閉合子圖,大概有這樣的問題結構—— 1,告訴你每個點的權值(權值有正有負) 2,點與點之間的選取,存在一定的依賴關係。 就比如說如果選了x,則必須要選擇另外一些點{p1, p2, p3},即{px}是x的必要條件。 3,你需要決定選出若干點,進而使得點權之和最大 建圖方式: 1,源點->正權點,容量為權值絕對值 2,負權點->匯點,容量為權值絕對值 3,對於如果選了x,就必須要選擇y的情況,我們從x向y建立一條容量為inf的邊 在這種情況下,我們跑最大流,得到最小割 與ST相連的點屬於最大權閉合子圖中的點,與ED相連的點不屬於最大權閉合子圖。 理解: 最大流 = 最小割,而最小割,意味著以最小成本,使得源點與匯點不連通。 在這種建圖方式下—— 割掉ST->正權點的邊,意味著記下來所對應的成本太大了,我們不如放棄該正權 割掉負權點->ED的邊,意味著我們雖然使用了這些成本,但是正權還沒有被畫完 而依賴關係之前的正無窮的邊, 意味著這前驅節點和後繼節點之間是不可同時割捨的 總的正權值 - 最小割, 就是問題的解 HDU3996 [題目] 我們要挖礦,礦的層次為n(100),第i層有金礦數mi。 對於每個金礦,告訴你—— 1.開採成本;2.開採收益;3.關聯前驅金礦數g(就是必須把這g個開採了才能開採當前金礦) 然後接下來g行,是相關聯金礦的層數和金礦編號。讓你求最大收益。 [分析] 直接裸建圖即可 HDU3879 [題目] 有n(5000)個點和m(50000)條邊 我們選擇每個點,會有一個相應的花費p[] 如果一條邊的兩個點都選中了,那麼我們便會得到一個相應的收益c[] 問你如何選擇點,才能使得自己的純利潤最大。 [分析] 把邊抽象為點 源點->邊(邊的收益) 邊->端點(inf) 端點->匯點(端點的成本) ∑正權 - 就是答案 HDU5855 [題目] 有n(200)個工廠和m(200)個商店 對於第i個工廠,工廠修建有成本c[i]和時間t[i] 對於第i個商店,如果其所需要的工廠都修建好了,其可以獲利v[i] 我們希望在最少的時間內獲利至少L(已知引數,int範圍) 要你輸出這個最小需要的時間t,然後在輸出在這個時間下的最大獲利值p [分析] 1,顯然我們可以二分時間,二分完時間之後,我們就知道哪些工廠可以考慮修建了。 2,接下來要建圖,以判斷是否能再這個時間內獲得至少為L的利潤 建圖方式—— 源點->商店(商店收益) 商店->工廠(無窮大) 工廠->匯點(建設成本) 跑最大權閉合子圖,sum-正權邊就是答案。 HDU5772 2016 Multi-University Training Contest 4I [題目] 給你一個長度為n(100)的數串,字元範圍為'0'~'9'。 對於數字x,如果字串中的有0個x,費用為0。 如果字串中的有k(k>0)個x,費用為bx+(kx-1)*ax。 也就是說——第一個的費用為bx,接下來的每個費用都為ax。 同時我們有一個n*n的表w[i][j], 表示如果我們選擇了位置i和位置j的字元,就會獲得收益w[i][j]。 [分析] 首先,這個費用函式有些奇怪。 如果費用函式沒這麼奇怪的話,就是對於x,如果費用是kx * ax的話—— 1,分析出來有一種特殊狀態:(i與j一起選,獲得收益w[i][j]+w[j][i]) 2,如果我們有這種特殊狀態,則我們意味著選i且選j,於是我們還要設定原始的每個點的選取情況。 即有(i&&j)->i(inf),(i&&j)->j(inf) 選位置意味著選數,即i -> num[i](inf),i -> num[j](inf) 注意:兩種邊不能可以合併,直接使得(i&&j) -> num[i](inf),(i&&j) -> num[j](inf) 因為選了num[i]和num[j],並不意味著我們選擇了位置i和位置j,所以還是要拆開考慮。 同時,選一個數會產生一定的費用,即這裡我們有i->ED(ax) 這種建圖模型可以解決kx * ax的問題,但是對於這道題中第一次選取成本為bx的情況就無法處理了。 於是我們考慮打補丁。 如果bx > ax,則意味著對於數字x,只要選了,在第一次選取的時候,會產生bx - ax的額外費用 於是我們只需要對於數字x,初始額外建一條x -> ED(bx - ax)的邊即可 如果bx < ax,則意味著對於數字x,只要選了,在第一次選取的時候,會減少ax - bx的費用 也就是說,會獲得ax - bx的收益,於是我們額外建一條ST -> x(ax - bx)的邊即可。 接下來∑正權 - dinic() 就是答案 【混合圖歐拉回路】 HDU1956 [題意] n(200)個點,m(1000)條邊, 對於每條邊,有屬性z,如果z == 1,則該邊為單向邊,否則該邊為雙向邊。 讓你判斷,在給每條雙向邊任意定向之後,能否在整個圖中產生歐拉回路。 [分析] 網路流可以解決混合圖求歐拉回路的問題 首先我們把雙向邊任意定向,並對於每個點,統計其在當前定向條件下的入度和出度。 如果對於任意點,(入度 - 出度) % 2 == 1,則無解。 否則我們設(ind[i] - oud[i]) / 2 = w 對於入度>出度的點(即w > 0),我們從這個點向ED連容量為abs(w)的邊使之平衡化 對於出度>入度的點(即w < 0),我們從ST向這個點連容量為abs(w)的邊使之平衡化 然後跑最大流。如果最大流為∑{w, w > 0},則表示問題有解。 理解: 對於入度 > 出度的點,其向ED連了容量為abs(w)的邊都要流滿 如果流滿了,流出的容量為出度+w = 入度-w,也就是說,我們改變1/2的入度的方向即可 對於入度 < 出度的點,其從ST連了容量為abs(w)的邊都要流滿 如果流滿了,流入的容量為入度+w = 出度-w,也就是說,我們改變1/2的出度的方向即可 於是這種建圖能對應得到問題的解。 如果要輸出方案,我們把所有從x到y沒有流量的邊反向即可 該邊流滿,說明我們選擇了這條邊;該邊未滿,說明這條邊需要反向 Codeforces Round 375 (Div 2) E One-Way Reform 雙向圖每條邊定向使得最多的點滿足入度=出度 [題意] http://codeforces.com/contest/723/problem/E n(200)個點m(C(n,2))條邊的無向連通圖,沒有自環沒有重邊 我們要把所有點都定向,希望使得儘可能多的點擁有相同的入度與出度。 讓你輸出滿足這個條件的最大點數和每條邊最後的定向。 [分析] 首先,有一個猜想—— 就是滿足那個條件的最大點數為擁有偶數度數的點數。 首先,度數為奇數的點,顯然不滿足條件。 其次,度數為偶數的點,可以以形成歐拉回路的方式使得條件滿足。 基於這個猜想,我們可以把度數為奇數的點配對連邊 (注意:一定要配對而不能直接從ST連一條邊了事,因為ST是特殊點) 這時的圖一定存在一個歐拉回路,也就使得每個點的入度=出度,使得我們的猜想成立 我們可以用歐拉回路演算法解決這道題,複雜度O(n+m) 也可以套用網路流解決,複雜度O(n*m) 網路流的建圖方式是—— 一開始把奇點之間配對連邊,再使得所有點任意定向, 對於任意一個點,設w=abs(ind[i]-oud[i])/2 如果ind[i]>oud[i],就從i向ED連容量為w的邊 如果oud[i]>ind[i],就從ST向i連容量為w的邊 這時跑下最大流,如果滿流,就存在歐拉回路 HDU5639 Deletion 每次最多刪環套樹最少刪除次數刪光所有邊 [題意] http://acm.hdu.edu.cn/showproblem.php?pid=5639 有一個無向圖G 圖上有n(2000)個點和m(2000)條邊 你可以選擇刪除一些邊,對於刪除的每個連通塊,該連通塊內,最多隻能含有一個環。 問你最小的刪除次數使得刪光所有邊。 [分析] 這道題我們思考最多隻能含有一個環是什麼東西——這個其實很熟悉,是環套樹。 對於環套樹,其可以是有向或無向的。對於有向環套樹,其實每個點都恰好一條出度就好了。 假如說我們給原始圖的每條邊定了向。那麼,該圖中一個最大的出度數w,就必然對應著有至少w個環,至少消w次 於是,我們可以把問題轉化——把無向圖的邊定向,使得最大點的出度儘可能小。 這個問題顯然可以二分答案,而二分答案為V之後,我們要如何驗證合法性呢? 把邊定向,有點類似於混合圖歐拉回路的求法。 我們設該點的出度為O 對於出度大於V的點,從源點向其連容量為O-V的邊 對於出度小於V的點,由其向匯點連容量為V-O的邊 然後我們跑最大流,如果最大流是滿流的,則說明可行 理解: 如果滿流,則說明對於出度大於V的點,我們把這些邊反過來之後,可以在一些出度小於V的點上消耗掉。 於是滿流就相當於滿足的了最大出度<=V的要求 */