1319】 CX‘s dreams 【最大權閉合圖 -- 最大化正點權個數】
CX有很多的夢想,但是沒有付出拿來的收穫。比如CX想擁有巧克力般的八塊腹肌,就要在健身房舉啞鈴以及跑步,這是很累的。比如CX要成為ACM大犇,就要刷題1000道,但CX是很享受刷題的,所以並不一定所有的付出都會讓CX感到很累、不喜歡。於是CX對每個夢想和每個付出進行了估值,每個夢想有其權值(非負數),代表實現這個夢想能收穫的權值,而每個夢想可能需要多個付出來滿足,並且一個付出不一定只對應一個夢想,夢想能夠實現只有其所需的付出都完成了才行。對於每個付出,也有一個權值(可正可負),代表CX完成這個付出能得到的權值(越小代表CX越不喜歡這個付出,所以負數代表CX有多討厭該付出,正數代表CX有多喜歡這個付出,為0代表說不上喜歡還是不喜歡)。CX是一個享受生活的人,希望得到的權值最多(完成付出得到的權值+實現夢想得到的權值),當有多個方式可以使權值最多時,希望能完成的夢想最多。現在給出每個夢想、付出的權值,以及夢想和付出的關係,請你求出CX能獲得的最大權值,以及在這個條件下的能夠實現的最多夢想數目。
Input
多組資料輸入(最多20組),輸入讀到檔案結束。
每組資料第一行兩個整數N,M。其中N代表CX有N(1<=N<=1000)個夢想可選則,M代表有M(1<=M<=1000)個付出可選擇。
接下來一行N個數,第i個數Ri(0<=wi<=10^5)代表第i個夢想實現了可以獲得的權值。
接下來一行M個數,第i個數Ci(-10^5<=Ci<=10^5)代表第i個付出的權值(正數代表完成該付出可以獲得權值,負數代表完成該付出會去掉權值)。
接下來N行,描述每個夢想所需要的付出,第一個數K(K<=min(50,M))代表該夢想所需要的付出數量,接下K個數,第i個數Pi代表其所需要的第i個付出為Pi(1<=Pi<=M)。
Output
每組輸入對應一行輸出,輸出兩個整數A B。
A代表所能獲得的最大權值,B代表能獲得這麼多權值下所能完成的最多夢想。
Sample Input
3 3
5 6 7
1 -10 -5
1 3
2 1 3
3 1 2 3
Sample Output
7 2
Hint
CSU_ZZY
分析: 首先對於正點權的付出,因為其沒有後繼,所以最小割肯定不會割這個邊,所以我們可以直接取而不用建邊 。 然後剩下的圖,我們就可以將它看做一個裸的最大權閉合圖模型了。
最大的權值很好處理。 關鍵就是那個最大化夢想個數,這個怎麼處理呢?
這個處理的方法好巧妙,Otz 。
先看一下 建圖方法,然後再討論分析: 設一個整數 C
S —-> 正點權的夢想 , cap為 C*dreamval +1
負點權的付出—->T ,cap為 C *workval
對於夢想和付出的關係部分邊 還是一樣的為INF
假設這樣跑下來的最小割為F
分析一下:因為要求的是S-T的最小割,而且割邊都是簡單割,所以如果有一種情況 割夢想的點權和割付出的點權一樣大(沒有上面的+1部分,確實會有這種情況),割哪個都可以,但是這裡我們加了1,這樣的話肯定會優先割付出的邊。 這樣不就正好是最大化夢想個數了嗎?
什麼? 你說為什麼不直接+1,還乘C幹嘛 ?其實是為了將C當成一種變相的模。如果我們乘了C,不+1的話,最後是不是肯定是C的倍數的流 F=k*C? 當然這樣也沒有什麼意義,但是如果我們再加上1,這樣的話,我們就會發現 ,最後肯定會多一些 流,而這些流 思考一下就會發現 它就是割的夢想個數(可以用F%C得到)。而最後的 真正的最小割權值應該為F/C (整數除法) .[ 從分析中,我們也可以發現C的大小一定要是 大於總夢想的個數,因為最後F最多多n個流,而我們同時要將其取模取出來]
程式碼
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
#define LL long long
const int N = 2e3+11;
const int M = 1e5+11;
const LL inf = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9+7;
const LL C = 1e5;
struct Edge {
int form,to,nexts;
LL cap,flow;
Edge(){}
Edge (int _form,int _to,LL _cap,LL _flow,int _nexts){
form=_form; to=_to; cap=_cap; flow=_flow; nexts=_nexts;
}
}edge[M];
int head[N],top;
void init(){
memset(head,-1,sizeof(head));
top=0;
}
void addedge(int a,int b,LL c){
edge[top]=Edge(a,b,c,0,head[a]); head[a]=top++;
edge[top]=Edge(b,a,0,0,head[b]); head[b]=top++;
}
int vis[N],dis[N];
int cur[N];
bool bfs(int st,int ed){
queue<int>Q;
memset(vis,0,sizeof(vis));
memset(dis,-1,sizeof(dis));
Q.push(st);vis[st]=1;dis[st]=1;
while(!Q.empty()){
int now=Q.front();Q.pop();
for(int i=head[now];i!=-1;i=edge[i].nexts){
Edge e=edge[i];
if(!vis[e.to]&&e.cap-e.flow>0){
vis[e.to]=1;
dis[e.to]=dis[now]+1;
if(e.to==ed) return 1;
Q.push(e.to);
}
}
}
return 0;
}
LL dfs(int now,LL a,int ed){
if(a==0||now==ed) return a;
LL flow=0,f;
for(int &i=cur[now];i!=-1;i=edge[i].nexts){
Edge &e=edge[i];
if(dis[e.to]==dis[now]+1&&(f=dfs(e.to,min(e.cap*1ll-e.flow,a),ed))>0){
e.flow+=f;
flow+=f;
edge[i^1].flow-=f;
a-=f;
if(a==0) break;
}
}
return flow;
}
LL max_flow(int st ,int ed){
LL flow=0;
while(bfs(st,ed)){
memcpy(cur,head,sizeof(head));
flow+=dfs(st,inf,ed);
}
return flow;
}
int work[N];
int main(){
int n,m;
while(scanf("%d%d",&n,&m)!=EOF){
init();
int S=0,T=n+m+1; LL ans=0;
for(int i=1;i<=n;i++){
LL z;scanf("%lld",&z);
addedge(S,i,z*C+1);
ans+=z;
}
for(int i=1;i<=m;i++){
LL z;scanf("%lld",&work[i]);
z=work[i];
if(z>=0) ans+=z;
else addedge(n+i,T,-z*C);
}
for(int i=1;i<=n;i++){
int k;scanf("%d",&k);
for(int j=1;j<=k;j++){
LL z;scanf("%lld",&z);
if(work[z]>=0) continue;
addedge(i,n+z,inf);
}
}
LL mx=max_flow(S,T);
LL ge=mx%C;
ans-=mx/C;
printf("%lld %lld\n",ans,n-ge);
}
return 0;
}