描述
周末,小Hi和小Ho所在的班級決定舉行一些班級建設活動。
根據周內的調查結果,小Hi和小Ho一共列出了N項不同的活動(編號1..N),第i項活動能夠產生a[i]的活躍值。
班級一共有M名學生(編號1..M),邀請編號為i的同學來參加班級建設活動需要消耗b[i]的活躍值。
每項活動都需要某些學生在場才能夠進行,若其中有任意一個學生沒有被邀請,這項活動就沒有辦法進行。
班級建設的活躍值是活動產生的總活躍值減去邀請學生所花費的活躍值。
小Hi和小Ho需要選擇進行哪些活動,來保證班級建設的活躍值盡可能大。
比如有3項活動,4名學生:
第1項活動產生5的活躍值,需要編號為1、2的學生才能進行;
第2項活動產生10的活躍值,需要編號為3、4的學生才能進行;
第3項活動產生8的活躍值,需要編號為2、3、4的學生才能進行。
編號為1到4的學生需要消耗的活躍值分別為6、3、5、4。
假設舉辦活動集合為{1},需要邀請的學生集合為{1,2},則得到的班級活躍值為5-9 = -4。
假設舉辦活動集合為{2},需要邀請的學生集合為{3,4},則得到的班級活躍值為10-9 = 1。
假設舉辦活動集合為{2,3},需要邀請的學生集合為{2,3,4},則得到的班級活躍值為18-12 = 6。
假設舉辦活動集合為{1,2,3},需要邀請的學生集合為{1,2,3,4},則得到的班級活躍值為23-18 = 5。
小Hi和小Ho總是希望班級活躍值越大越好,因此在這個例子中,他們會選擇舉行活動2和活動3。
提示:最大權閉合子圖
小Ho:這次的問題好像還是很麻煩的樣子啊。
小Hi:沒錯,小Ho你有什么想法么?
小Ho:我么?我能想到只有枚舉啦。因為每一項活動都只有舉行和不舉行兩種狀態,因此我直接用O(2^N)的枚舉,再對選出來的情況進行計算。最后選出最大的方案。
小Hi:這很明顯會超過時間限制吧。
小Ho:我知道啊,那有什么好的方法么?
小Hi:當然有啊,這次我們需要解決的是閉合子圖問題。
小Ho:這個閉合子圖是啥?
小Hi:所謂閉合子圖就是給定一個有向圖,從中選擇一些點組成一個點集V。對於V中任意一個點,其后續節點都仍然在V中。比如:
在這個圖中有8個閉合子圖:∅,{3},{4},{2,4},{3,4},{1,3,4},{2,3,4},{1,2,3,4}
小Ho:閉合子圖我懂了,但是這跟我們這次的問題有啥關系呢?
小Hi:我們先把這次的問題轉化為2分圖。將N個活動看作A部,將M個學生看作B部。若第i個活動需要第j個學生,就連一條從A[i]到B[j]的有向邊。比如對於例子:
假如選擇A[1],則我們需要同時選擇B[1],B[2]。那么選擇什么活動和其需要的學生,是不是就剛好對應了這個圖中的一個閉合子圖呢?
小Ho:你這么一說好像還真是。如果把活躍值算作權值,A部的節點包含有正的權值,B部的節點是負的權值。那么我們要求的也就是一個權值最大的閉合子圖了?
小Hi:沒錯,我們要求解的正是最大權閉合子圖。它的求解方法是使用網絡流,因此我們需要將這個圖再進一步轉化為網絡流圖。
對於一般的圖來說:首先建立源點s和匯點t,將源點s與所有權值為正的點相連,容量為權值;將所有權值為負的點與匯點t相連,容量為權值的絕對值;權值為0的點不做處理;同時將原來的邊容量設置為無窮大。舉個例子:
對於我們題目中的例子來說,其轉化的網絡流圖為:
上圖中黑邊表示容量無窮大的邊。
小Ho:轉化模型這一步看上去不是太難,然后呢?
小Hi:先說說結論吧,最大權閉合子圖的權值等於所有正權點之和減去最小割。
接下來來證明這個結論,首先我們要證明兩個引理:
1. 最小割一定是簡單割
簡單割指得是:割(S,T)中每一條割邊都與s或者t關聯,這樣的割叫做簡單割。
因為在圖中將所有與s相連的點放入割集就可以得到一個割,且這個割不為正無窮。而最小割一定小於等於這個割,所以最小割一定不包含無窮大的邊。因此最小割一定一個簡單割。
2. 簡單割一定和一個閉合子圖對應
閉合子圖V和源點s構成S集,其余點和匯點t構成T集。
首先證明閉合子圖是簡單割:若閉合子圖對應的割(S,T)不是簡單割,則存在一條邊(u,v),u∈S,v∈T,且c(u,v)=∞。說明u的后續節點v不在S中,產生矛盾。
接着證明簡單割是閉合子圖:對於V中任意一個點u,u∈S。u的任意一條出邊c(u,v)=∞,不會在簡單割的割邊集中,因此v不屬於T,v∈S。所以V的所有點均在S中,因此S-s是閉合子圖。
由上面兩個引理可以知道,最小割也對應了一個閉合子圖,接下來證明最小割就是最大權的閉合子圖。
首先有割的容量C(S,T)=T中所有正權點的權值之和+S中所有負權點的權值絕對值之和。
閉合子圖的權值W=S中所有正權點的權值之和-S中所有負權點的權值絕對值之和。
則有C(S,T)+W=T中所有正權點的權值之和+S中所有正權點的權值之和=所有正權點的權值之和。
所以W=所有正權點的權值之和-C(S,T)
由於所有正權點的權值之和是一個定值,那么割的容量越小,W也就越大。因此當C(S,T)取最小割時,W也就達到了最大權。
小Ho:我懂了,因為最小割也對應了一個閉合子圖,因此它是可以被取得的,W也才能夠到達最大權值。
小Hi:沒錯,這就是前面兩條引理的作用。
小Ho:那么最小割的求解就還是用最大流來完成好了!
小Hi:嗯,那就交給你了。

#include <bits/stdc++.h> using namespace std; #define maxn 505 #define INF 0x3f3f3f3f struct Edge { int from,to,cap,flow; }; struct Dinic { int n,m,s,t; vector<Edge> edge; vector<int> G[maxn]; bool vis[maxn]; int d[maxn]; int cur[maxn]; void init() { for(int i=0;i<maxn;i++) G[i].clear(); edge.clear(); memset(d,0,sizeof(d)); memset(vis,0,sizeof(vis)); memset(cur,0,sizeof(cur)); } void addEdge (int from,int to,int cap) { edge.push_back((Edge){from,to,cap,0}); edge.push_back((Edge){to,from,0,0}); m = edge.size(); G[from].push_back(m-2); G[to].push_back(m-1); } bool BFS() { memset(vis,0,sizeof(vis)); queue<int> Q; Q.push(s); d[s] = 0; vis[s] = 1; while(!Q.empty()) { int x = Q.front(); Q.pop(); for(int i=0; i<G[x].size(); i++) { Edge & e = edge[G[x][i]]; if(!vis[e.to]&&e.cap>e.flow) { vis[e.to] = 1; d[e.to] = d[x] + 1; Q.push(e.to); } } } return vis[t]; } long long DFS(int x,int a) { if(x==t||a==0) return a; long long flow = 0,f; for(int & i = cur[x]; i<G[x].size(); i++) { Edge & e = edge[G[x][i]]; if(d[x] + 1==d[e.to]&&(f=DFS(e.to,min(a,e.cap-e.flow)))>0) { e.flow +=f; edge[G[x][i]^1].flow -=f; flow +=f; a-=f; if(a==0) break; } } return flow; } int Maxflow (int s,int t) { this->s = s;this->t = t; int flow = 0; while(BFS()) { memset(cur,0,sizeof(cur)); flow+=DFS(s,INF); } return flow; } //求最小割S,T; void new_BFS(int s,int n) { memset(vis,0,sizeof(vis)); d[s] = 0; vis[s] = 1; queue<int> Q; Q.push(s); while(!Q.empty()) { int u = Q.front(); Q.pop(); for(int i=0;i<G[u].size();i++) { Edge & e = edge[G[u][i]]; if(!vis[e.to]&&e.cap>e.flow) { vis[e.to] = 1; d[e.to] = d[u] + 1; Q.push(e.to); } } } int cnt = 0; for(int i=1;i<=n;i++) { if(vis[i]) cnt++; } printf("%d\n",cnt); for(int i=1;i<=n;i++) if(vis[i]) printf("%d ",i); puts(""); } }sol; int main() { sol.init(); int n; int m; scanf("%d%d",&n,&m); int s=0; int t=n+m+1; int cap; for(int i=1; i<=m; i++) { scanf("%d",&cap); sol.addEdge(n+i,t,cap); } int sum = 0; for(int i=1; i<=n; i++) { int a,k; scanf("%d%d",&a,&k); sum+=a; sol.addEdge(s,i,a); for(int j=0; j<k; j++) { int v; scanf("%d",&v); sol.addEdge(i,v+n,INF); } } printf("%d\n",sum-sol.Maxflow(s,t)); return 0; }
閉合子圖: 就是按圖1說的,1加到集合里面去了,那么與之相連的3,4必須到集合中去,那么一張圖就有很多個閉合子圖。
然后每個點都有一個權值,要求一個權值最大的子圖。
結論:最大權閉合子圖 = 正點和-最小割。
分析:
1,一個最小割肯定是一個子圖。證明上面有,但是我沒看懂。
2,有了上一點:
割的容量C(S,T) = S中的正權點之和+ T中負權點絕對值之和。
閉合子圖的權值W = S中的正權點之和 - S中負權點絕對值之和。
可以推出 W = 所有正權點之和 - C(S,T);
還是看圖說話吧:
最大流 = 最小割 = 17,正點之和 = 23,最大權閉合子圖 = 23-17 = 6;