网络流及建模专题(上)
前言
不斷更新中……
這幾天新坑填不下去了,回來回顧一些經典的模型套路,先拿網絡流開刀,竊以為洛谷這幾道網絡流的題目還是非常具有代表性的,涵蓋了網絡流調整、多解計數、最小割、最大權閉合子圖問題。
還涵蓋了圖論(二分圖)中的一些結論和:最小不相交路徑覆蓋、最小可相交路徑覆蓋、二分圖最大點權獨立集、二分圖最小點權覆蓋集等問題,這里將簡略介紹一下。
本專題包含六道題:P2765、P2764、P2763、P2766、P2774、P2805
P2765 網絡流調整技巧
P2763 二分圖多重匹配問題
P2764 最小不相交路徑覆蓋、最小可相交路徑覆蓋
P2766 一類dp計數問題、最多不相交路徑
P2774 二分圖最大點權獨立集、二分圖最小點權覆蓋集
P2805 最大權閉合子圖
前提
- 知道網絡流的基本用法。
- 知道最大流最小割定理。
- 知道二分圖的定義。
- 有一個穩定好用的Dinic求網絡流的板子。
P2765 魔術球問題
前言
這道題應該可以通過貪心的方法過,不過既然放在了網絡流專題里面,我們就認真的用網絡流試一試并總結一下經驗。
題解
添加一個小球相當于在網絡中出現了一個從源點SSS到匯點TTT的流量。
分析一下題目:同一個柱子的小球要求可以與前一個小球形成和為完全平方數的情況,當一個新的小球到來的時候,可以加入其中的一根柱子,當然也可以另外開啟一個新的柱子,使用的柱子的總數不得超過nnn。
我們另開一個節點T′T'T′用來表示柱子的上限,連接一條容量為nnn的邊(T′,T)(T',T)(T′,T)。
我們嘗試把小球拆成兩個點BBB和B′B'B′,并且由SSS向BBB連接一條容量為111的邊。由B′B'B′向TTT連接一條容量為111的邊,為什么容量為111?這限制了每個球后面都只能緊鄰地放一個球。
如果數大的小球與某個數小的小球之間組合滿足和為完全平方數(即大球可以放在小球后面),我們就從連接一條容量為111的邊(B大球,B小球′)(B_{大球},B_{小球}')(B大球?,B小球′?)。這代表該大球可以放在小球后面不需要開啟新的柱子。
每個球都可以連接一條容量為111的邊(B,T′)(B,T')(B,T′)。代表該小球獨立開啟了一個新的柱子。
如下圖所示:
回顧一下這張圖:
從BBB流向T′T'T′的流量代表BBB球開啟了一個新的柱子,而從B大球B_{大球}B大球?到B小球′B_{小球}'B小球′?的流量代表B大球B_{大球}B大球?放在了B小球B_{小球}B小球?的后面。
這只是建圖模型,我們在解決這道題的時候不能跑一遍網絡流完事,而是需要使用迭代加深搜索的思想,增量式建圖,每次加入一個球,然后在殘余網絡中跑最大流,判斷能否跑滿,如果跑不滿,那么答案就是上次跑滿時候的結果。
最后,這道題要求輸出方案,我們只需要在殘余網絡里面遍歷去邊的容量,然后就可以得到方案了,這里不必細說,看我的代碼就可以了。
參考代碼 見附錄
放置、匹配的問題,都可以往網絡流方面思考。
P2764 最小路徑覆蓋
什么是最小路徑覆蓋?
最小路徑覆蓋分為兩種
- 最小不相交路徑覆蓋
- 最小可相交路徑覆蓋
最小不相交路徑覆蓋的做法
構造一個新圖,將一個點BBB拆成B1B_1B1?和B2B_2B2?兩個點,在原圖中如果有邊(A,B)(A,B)(A,B)那么在新圖中連接邊(A1,B2)(A_1,B_2)(A1?,B2?)。
然后形成了一張二分圖,結論就是最小不相交路徑覆蓋=原圖節點數-二分圖的最大匹配。
如何理解這個過程呢?
一開始每個點都是一條路徑。
在二分圖中,每形成一個匹配,就相當于一條路徑將匹配兩邊的點給合并了,也就是合并了兩個路徑,這樣一直匹配下去,路徑數就在不斷的減少,所以最終形成的就是最小路徑覆蓋,最后的路徑數就是一開始原圖的節點數-匹配數。
最小可相交路徑覆蓋的做法
這個問題與本題沒有關系,只是擴展,可以暫時跳過。
解這個小問的方法是把這個問題化歸到最小不相交路徑覆蓋上去。
可相交也就是說如果兩點可達,那么不管這兩點中間有多少節點,我都可以去經過。
先跑出可達矩陣,用Floyd算法,算出兩兩點之間的可達性。
然后如果兩兩可達,則建邊。建成新圖以后,就變成了最小不相交路徑覆蓋問題了。
理解
但是如果兩個點a和b是連通的,只不過中間需要經過其它的點,那么可以在這兩個點之間加邊,那么a就可以直達b,不必經過中點的,那么就轉化成了最小不相交路徑覆蓋。
這道題同樣需要輸出方案,在殘余網絡中遍歷邊處理就可以了。
參考代碼 見附錄
P2763 試題庫問題
我覺得這是一個非常典型的二分圖問題。
kkk種類型是二分圖左側的kkk個點。
nnn個試題是二分圖右側的nnn個點。
如果某試題屬于某種類型,就在試題和類型之間連接一條邊,容量為111。
要求選出numnumnum個ttt類型的題目,那么就在源點SSS和ttt類型點之間加入一條容量為numnumnum的邊。
而每道題目只能使用一次,因此在題目ppp和匯點TTT之間連接一條容量為111的邊。
然后跑一遍最大流就可以得到答案了。
參考代碼見附錄
P2766 最長不下降子序列問題
這類問題屬于動態規劃方程已知,對解方案計數的問題。
還有的是要求最短路的數量的問題,我之前有在我的博客里寫過,感興趣的各位可以找找看。
第一問
要求求出最長不下降子序列長度,這個使用動態規劃很容易解決,但是我們必須要在這一步中處理出以a[i]a[i]a[i]元素為結尾的最長非降子序列的長度數組f[i]f[i]f[i]。
第二問
要求在所有的點都不能重復使用的時候,能找到多少個最長非減子序列。
在網絡流處理問題的模型中,如果一個點不能重復利用,那么我們可以將這個點PPP拆成兩個點PsP_sPs?和PtP_tPt?,并由PsP_sPs?向PtP_tPt?連接一條容量為111的邊。所有流入這個點的邊都與PsP_sPs?相連,所有流出這個點的邊都與PtP_tPt?相連。
在這個題中,我們從f[i]f[i]f[i]數組出發,建立一個DAG(有向無環圖)。即滿足f[j]+1=f[i]f[j]+1=f[i]f[j]+1=f[i]且a[j]≤a[i]a[j] ≤ a[i]a[j]≤a[i]的(j,i)(j,i)(j,i)我們都建立一條iii到jjj的容量為infinfinf的邊。
然后建立一個超級源點SSS,和超級匯點TTT,SSS向f[i]==mxlenf[i] == mxlenf[i]==mxlen的點iii連邊,TTT向f[i]==1f[i] == 1f[i]==1的點iii連邊,容量均為111。
然后按照我上面說的方法拆點。
舉例:
3,6,2,53,6,2 ,53,6,2,5
這個序列的mxlen=2mxlen=2mxlen=2,f[]=1,2,1,2f[]={1,2,1,2}f[]=1,2,1,2,我們建成的圖就是:
這樣的話,跑一遍最大流就知道有多少條長度為mxlenmxlenmxlen的不相交的路徑了。
第三問
若某個點可以無限用的話,那么這個點就不應該被拆。
對第二問的圖稍做修改,如下
這里有個小細節當xnx_nxn?和x1x_1x1?直接相連接的時候,他們之間的邊權不能設為 infinfinf而應該是111
另一個小細節就是如果mxlen==1mxlen==1mxlen==1那么直接輸出nnn不需要建圖。
參考代碼 見附錄
P2774 方格取數問題
由這個問題引出的模型是二分圖最大點權獨立集。
二分圖點權最大獨立集:帶點權二分圖G中的一個子集V,其中一條邊的兩個端點不能同時屬于V,且V中點權和最大。
與二分圖最大點權獨立集對偶問題的是二分圖的最小點權覆蓋集,即用最小點權和的點集去覆蓋所有的邊,解法就是按照如下的方法建圖跑最小割。
相鄰的格子數不能同時取到,他們兩個是互斥的,我們對格子進行黑白染色以后,形成了一張二分圖,這個問題轉化為求這張二分圖的最大點權獨立集。
這種模型可以采用求最小割的方法來做
從SSS點向白點連邊,容量為白點的權值,從黑點向TTT點連邊,容量為黑點的權值。
白點和黑點之間如果有互斥關系,那么從白點向黑點連接一條容量為infinfinf的邊,形成一張網絡圖。
我們觀察這張圖的最小割,我們發現容量為infinfinf的邊一定不能被割掉,那么這條邊兩端的點u,vu,vu,v就必然有(S,u)(S,u)(S,u)被割掉或者是(v,T)(v,T)(v,T)被割掉,被割掉的含義就是這個點的權值我不要了。由于我們得到的是最小割,也就是說我們剩下的點的權值之和一定是最大的,且不互斥的,因此用總的權值和減去最小個的值就是最大權獨立集的權值和,也就是本題的答案。
例如下圖的答案就是
ans=(a+b+c+d+e+f)?(a+b+d+e)=c+fans = (a+b+c+d+e+f) - (a+b+d+e) = c+fans=(a+b+c+d+e+f)?(a+b+d+e)=c+f
##參考代碼 見附錄
P2805 植物大戰僵尸
這個問題要引出的模型就更厲害啦!
那就是:
最大權閉合子圖
最大權閉合子圖
最大權閉合子圖
重要的問題說三遍,這個模型很重要,能解決非常多有趣的問題,主要被用來解決在有依賴性條件下的規劃問題。
什么是最大權閉合子圖?
閉合子圖的定義是:在子圖中,所有點的出邊所指向的節點仍在該子圖內。
在這張圖中,閉合子圖有:
{E}、{D,E}、{F,E}、{A,D,E}、{B,D,E}、{F,E}、{C,F,E}\{E\}、\{D,E\}、\{F,E\}、\{A,D,E\}、\{B,D,E\}、\{F,E\}、\{C,F,E\}{E}、{D,E}、{F,E}、{A,D,E}、{B,D,E}、{F,E}、{C,F,E}
最大權閉合子圖就是找一個最大的權值和的閉合子圖。
如何找最大權閉合子圖?
我們把這個圖進行變形,變形成可以用最小割解決的問題,然后用最小割解決它。
變形方法:**如果一個點的權值為正,那么從SSS點向該點連接一條容量為權值的邊,如果這個點的權值為負,那么從該點向TTT點連接一條容量為權值絕對值的邊,原圖中的所有邊容量均設置為infinfinf。**然后跑一遍最小割,原圖中正的點權和減去最小割得到的就是最大權閉合子圖。
證明這里就不證明了,有興趣的同學可以去網上搜搜看。
如何解決依賴關系下的選取問題?
如果要選取物品AAA必須要先選取物品BBB,則說物品AAA依賴于物品BBB,反映在圖中就是AAA到BBB連有一條邊。
選取某個物品可能獲利也有可能要付出代價,如果獲利則說該物品的權值符號為正,如果獲取該物品要付出代價,則說該物品的權值符號為負。
而一種選取方案則必然屬于原圖中的一個閉合子圖,如果不閉合這個選取方案必然非法。
因此,求一種獲利最大的選取方案就相當于在求一個最大權閉合子圖。
這道題怎么做?
這道題中,僵尸只能從每一行的最右端開始吃,想吃(x,y)(x,y)(x,y)位置的植物必須先吃掉(x,y+1)(x,y+1)(x,y+1)位置的植物,這樣的話,就形成了一個依賴關系(x,y)(x,y)(x,y)依賴于(x,y+1)(x,y+1)(x,y+1),寫作(x,y)?>(x,y+1)(x,y)->(x,y+1)(x,y)?>(x,y+1)。
本題中還有另外的一個依賴關系,那就是某植物AAA可以保護其他一些植物BBB,也就是存在依賴關系:(xB,yB)?>(xA,yA)(x_B,y_B)->(x_A,y_A)(xB?,yB?)?>(xA?,yA?)。
根據這些依賴關系,以及吃掉植物的獲利,我們可以建立一張圖。
然后在這張圖求一個最大權閉合子圖,就是我們的答案。
但是我們忽略了一點,就是說可能存在無敵植物,這些植物的依賴關系形成了一個環,這樣就不能直接跑最大權閉合子圖了,所以,我們第一步要先去掉這張依賴關系圖中的環。使用什么來去環呢?答案是拓撲排序。
先觀察可能出現環的情形:
這種情形下,環應該被去掉,因為環代表無敵關系。而依賴于環的點JJJ也應該被去掉,因為環被去掉了以后這個依賴關系永遠不能成立。
如何去掉環和依賴于環的點?
其實很簡單,我們將圖中所有的邊反向,使用拓撲排序,將所有沒有被訪問的節點去掉。
這一步的核心操作就在反向。
去掉點之后,直接跑最大權閉合子圖就可以了。
參考代碼 見附錄
附錄
Dinic網絡流模板
以下代碼均不含模板
const int inf = 1e9; const int maxm = 800001; const int maxn = 10001; int node,src,dest,edge; int ver[maxm],flow[maxm],nxt[maxm]; int head[maxn],work[maxn],dis[maxn],q[maxn]; void prepare(int _node,int _src,int _dest) {node=_node,src=_src,dest=_dest;for(int i=0; i<node; ++i)head[i]=-1;edge=0; } void addedge(int u,int v,int c) {ver[edge]=v,flow[edge]=c,nxt[edge]=head[u],head[u]=edge++;ver[edge]=u,flow[edge]=0,nxt[edge]=head[v],head[v]=edge++; } bool Dinic_bfs() {int i,u,v,l,r=0;for(i=0; i<node; ++i)dis[i]=-1;dis[q[r++]=src]=0;for(l=0; l<r; ++l)for(i=head[u=q[l]]; i>=0; i=nxt[i])if(flow[i]&&dis[v=ver[i]]<0){dis[q[r++]=v]=dis[u]+1;if(v==dest)return 1;}return 0; } int Dinic_dfs(int u,int exp) {if(u==dest)return exp;for(int &i=work[u],v,tmp; i>=0; i=nxt[i])if(flow[i]&&dis[v=ver[i]]==dis[u]+1&&(tmp=Dinic_dfs(v,min(exp,flow[i])))>0){flow[i]-=tmp;flow[i^1]+=tmp;return tmp;}return 0; } int Dinic_flow() {int i,ret=0,delta;while(Dinic_bfs()){for(i=0; i<node; ++i)work[i]=head[i];while(delta=Dinic_dfs(src,inf))ret+=delta;}return ret; }P2765
// 網絡流模板省去 int n,m = 2000; int main(){scanf("%d",&n);prepare(2*m+3,0,2*m+2);addedge(2*m+1,2*m+2,n);for(int i = 1;;++i){addedge(0,2*i-1,1);addedge(2*i-1,2*m+1,1,1);addedge(2*i,2*m+2,1);for(int j = 1;j < i;++j){int rt = sqrt(i+j+0.5);if(rt*rt == i+j) addedge(2*i-1,2*j,1,2);}int ans = Dinic_flow();if(ans < i){printf("%d\n",i-1);int vc = 0;//生成方案vector<int> G[n];vector<int> bl(i,-1);for(int e = 0;e < edge;e += 2){if(flow[e] != 0) continue;if(tps[e] == 1){int u = (ver[e+1]+1)/2;G[vc].push_back(u);bl[u] = vc;vc++;}else if(tps[e] == 2){int u = (ver[e+1]+1)/2;int v = (ver[e]+1)/2;bl[u] = bl[v];G[bl[u]].push_back(u);}}for(int i = 0;i < n;++i){for(auto v : G[i]) printf("%d ",v);puts("");}break;}}return 0; }P2764
int pnxt[maxn]; int deg[maxn]; int n,m; int main(){cin>>n>>m;prepare(2*n+2,0,2*n+1);for(int i = 0;i < m;++i){int u,v;scanf("%d%d",&u,&v);addedge(u,n+v,1);}for(int i = 1;i <= n;++i) addedge(0,i,1),addedge(i+n,2*n+1,1);int ans = Dinic_flow();for(int e = 0;e < 2*m;e += 2){if(flow[e] == 0){int u = ver[e+1];int v = ver[e] - n;pnxt[u] = v;deg[v]++;}}for(int i = 1;i <= n;++i){if(deg[i]) continue;int v = i;while(v){printf("%d ",v);v = pnxt[v];}puts("");}cout<<n-ans<<endl;return 0; }P2763
int n,k; int kn[30]; int main(){cin>>k>>n;prepare(1+k+n+1,0,1+k+n);for(int i = 1;i <= k;++i) cin>>kn[i];for(int i = 1;i <= k;++i) addedge(0,i,kn[i]);for(int i = 1;i <= n;++i){int num;scanf("%d",&num);for(int j = 1;j <= num;++j){int tt;scanf("%d",&tt);addedge(tt,i+k,1,1);}}for(int i = 1;i <= n;++i) addedge(i+k,1+k+n,1);int ans = Dinic_flow();vector<int> G[30];for(int e = 0;e < edge;++e){if(flow[e] == 0 && tps[e]){int u = ver[e+1];int v = ver[e]-k;G[u].push_back(v);}}for(int i = 1;i <= k;++i){printf("%d: ",i);for(auto v : G[i]) printf("%d ",v);puts("");}return 0; }P2766
int n,dp[507],f[507],a[507]; int main(){memset(dp,0x3f,sizeof(dp));cin>>n;for(int i = 1;i <= n;++i){int tmp;cin>>tmp;a[i] = tmp;int loc = upper_bound(dp+1,dp+1+n,tmp) - dp;f[i] = loc;dp[loc] = tmp;}int s = lower_bound(dp+1,dp+1+n,0x3f3f3f3f)-dp-1;cout<<s<<endl;prepare(2*n+2,0,2*n+1);for(int i = n;i >= 1;--i){addedge(i,i+n,1);if(f[i] == s) addedge(0,i,505);if(f[i] == 1) addedge(i+n,2*n+1,505);for(int j = i-1;j >= 1;--j){if(f[j]+1 == f[i] && a[j] <= a[i]) addedge(i+n,j,505);}}cout<<Dinic_flow()<<endl;if(s == 1) {return 0*printf("%d\n",n);}prepare(2*n+2,0,2*n+1);for(int i = n;i >= 1;--i){addedge(i,i+n,1);if(f[i] == s){if(i == n) addedge(0,i+n,505);else addedge(0,i,505);} if(f[i] == 1){if(i == 1) addedge(i,2*n+1,505);else addedge(i+n,2*n+1,505);}for(int j = i-1;j >= 1;--j){if(j == 1 && i == n) continue;if(f[j]+1 == f[i] && a[j] <= a[i]) addedge(i+n,j,505);}}int ans = Dinic_flow();if(f[1] + 1 == f[n]) ans ++;cout<<ans<<endl; }P2774
int n,m; int a[101][101]; int dx[] = {-1,1,0,0}; int dy[] = {0,0,-1,1}; int main(){long long sum = 0;cin>>m>>n;for(int i = 1;i <= m;++i){for(int j = 1;j <= n;++j) {scanf("%d",&a[i][j]);sum += a[i][j];}}prepare(n*m+2,0,n*m+1);for(int i = 1;i <= m;++i){for(int j = 1;j <= n;++j){int u = (i-1)*n+j;if((i+j)%2 == 0) addedge(0,u,a[i][j]);else addedge(u,n*m+1,a[i][j]);if((i+j)%2 == 0)for(int t = 0;t < 4;++t){int nx = i + dx[t],ny = j + dy[t];if(nx < 1 || nx > m || ny < 1 || ny > n) continue;int v = (nx-1)*n+ny;addedge(u,v,inf);}}}cout<<sum - Dinic_flow()<<endl;return 0; }P2805
typedef pair<int,int> pii; int a[100][100]; vector<pii> ps[100][100]; int n,m; long long sum; #define pr(x) cout<<#x<<":"<<x<<endl int main(){//freopen("ts3.txt","r",stdin);cin>>n>>m;prepare(n*m+2,0,n*m+1);for(int i = 1;i <= n;++i){for(int j = 1;j <= m;++j){int num;scanf("%d%d",&a[i][j],&num);for(int t = 0;t < num;++t){int x,y;scanf("%d%d",&x,&y);x++,y++;ps[i][j].push_back(make_pair(x,y));}if(j != 1) ps[i][j].push_back(make_pair(i,j-1));}}void toposort();toposort();long long ans = (long long)Dinic_flow();cout<<sum-ans<<endl; } int deg[30][31],vis[31][31],f[31][31]; void toposort(){queue<pii> Q;for(int ux = 1;ux <= n;++ux) for(int uy = 1;uy <= m;++uy){for(auto p : ps[ux][uy]) { deg[p.first][p.second] ++;}}for(int ux = 1;ux <= n;++ux) for(int uy = 1;uy <= m;++uy){if(!deg[ux][uy]) Q.push(make_pair(ux,uy));}while(!Q.empty()){pii p = Q.front();Q.pop();sum += max(0,a[p.first][p.second]); vis[p.first][p.second] = 1;for(auto p2:ps[p.first][p.second]){deg[p2.first][p2.second]--;if(deg[p2.first][p2.second]==0) Q.push(p2);}}for(int ux = 1;ux <= n;++ux) for(int uy = 1;uy <= m;++uy){if(!vis[ux][uy]) continue;for(auto p : ps[ux][uy]) {if(vis[p.first][p.second]) addedge((p.first-1)*m+p.second,(ux-1)*m+uy,inf);}if(a[ux][uy] >= 0) addedge(0,(ux-1)*m+uy,a[ux][uy]);else addedge((ux-1)*m+uy,n*m+1,-a[ux][uy]);}}原創:西安交大 蔡少斐
總結
以上是生活随笔為你收集整理的网络流及建模专题(上)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 新电脑买回来要怎么做小白新电脑买回来要怎
- 下一篇: 学徒表哥的电脑坏了寄过来维修学徒表哥的电