W 教授正在爲國家航天中心計劃一系列的太空飛行。每次太空飛行可進行一系列商業性實驗而獲取利潤。現已肯定了一個可供選擇的實驗集合 E={E1,E2,…,Em},和進行這些實驗須要使用的所有儀器的集合I={I1, I2,…In}。 實驗 Ej須要用到的儀器是I的子集。配置儀器Ik的費用爲ck美圓。實驗Ej的贊助商已贊成爲該實驗結果支付pj美圓。W教授的任務是找出一個有效算法, 肯定在一次太空飛行中要進行哪些實驗並所以而配置哪些儀器才能使太空飛行的淨收益最大。這裏淨收益是指進行實驗所得到的所有收入與配置儀器的所有費用的差額。node
【編程任務】:ios
對於給定的實驗和儀器配置狀況,編程找出淨收益最大的試驗計劃。git
輸入文件的第1行有 2 個正整數 m和n(0 < m,n <= 100)。m是實驗數,n是儀器數。接下來的 m 行,每行是一個實驗的有關數據。第一個數贊助商贊成支付該實驗的費用;接着是該實驗須要用到的若干儀器的編號,以一個0做爲行的結束標記。最後一行的 n個數是配置每一個儀器的費用。算法
輸出文件的第1行是實驗編號;第2行是儀器編號;最後一行是淨收益。編程
2 3網絡
10 1 2 0spa
25 2 3 03d
5 6 7code
1 2blog
1 2 3
17
週末,小Hi和小Ho所在的班級決定舉行一些班級建設活動。
根據周內的調查結果,小Hi和小Ho一共列出了N項不一樣的活動(編號1..N),第i項活動可以產生a[i]的活躍值。
班級一共有M名學生(編號1..M),邀請編號爲i的同窗來參加班級建設活動須要消耗b[i]的活躍值。
每項活動都須要某些學生在場纔可以進行,若其中有任意一個學生沒有被邀請,這項活動就沒有辦法進行。
班級建設的活躍值是活動產生的總活躍值減去邀請學生所花費的活躍值。
小Hi和小Ho須要選擇進行哪些活動,來保證班級建設的活躍值儘量大。
好比有3項活動,4名學生:
第1項活動產生5的活躍值,須要編號爲一、2的學生才能進行;
第2項活動產生10的活躍值,須要編號爲三、4的學生才能進行;
第3項活動產生8的活躍值,須要編號爲二、三、4的學生才能進行。
編號爲1到4的學生須要消耗的活躍值分別爲六、三、五、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<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<cstdio> #include<iomanip> #include<cstdlib> #define MAXN 0x7fffffff typedef long long LL; const int N=205; using namespace std; inline int Getint(){register int x=0,f=1;register char ch=getchar();while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}return x*f;} int n,m,S,T,num; struct node{int next,to,pair,flow;}g[N*N]; int h[N],cnt; void AddEdge(int x,int y,int z){ g[++cnt].to=y,g[cnt].next=h[x],h[x]=cnt,g[cnt].flow=z,g[cnt].pair=cnt+1; g[++cnt].to=x,g[cnt].next=h[y],h[y]=cnt,g[cnt].flow=0,g[cnt].pair=cnt-1; } int ans,GAP[N],dis[N]; int Dfs(int x,int Maxf){ if(x==T||!Maxf)return Maxf; int ret=0; for(int i=h[x];i;i=g[i].next){ int to=g[i].to; if(g[i].flow&&dis[x]==dis[to]+1){ int dlt=Dfs(to,min(g[i].flow,Maxf-ret)); g[i].flow-=dlt; g[g[i].pair].flow+=dlt; ret+=dlt; if(dis[S]==num||ret==Maxf)return ret; } } if(!(--GAP[dis[x]]))dis[S]=num; else GAP[++dis[x]]++; return ret; } int SAP(){ int ans=0; while(dis[S]<num)ans+=Dfs(S,MAXN); return ans; } #include<queue> priority_queue<int,vector<int>,greater<int> >q; bool vis[N]; void Find(int x){ if(x)q.push(x); vis[x]=1; for(int i=h[x];i;i=g[i].next){ int to=g[i].to; if(g[i].flow&&!vis[to])Find(to); } } int sum=0; int main(){ m=Getint(),n=Getint(),S=0,T=n+m+1,num=n+m+2; for(int i=1;i<=m;i++){ int x=Getint();sum+=x; AddEdge(S,i,x); for(int x=Getint();x;x=Getint())AddEdge(i,x+m,MAXN); } for(int i=1;i<=n;i++)AddEdge(i+m,T,Getint()); int ret=SAP();Find(S); while(q.top()<=m)cout<<q.top()<<' ',q.pop();cout<<'\n'; while(!q.empty())cout<<q.top()-m<<' ',q.pop();cout<<'\n'; cout<<sum-ret; return 0; }