有m个项目和n个员工,做项目i可以获得Ai元,但是必须雇用若干指定的员工。雇用员工j需要Bj元,一旦雇用便可以参与多个项目。问最大收益。
1<=M,N<=100.
最小割。
源点向每个项目点连一条容量为项目获利的边。
每个项目点向本项目需要的所有员工点连一条容量为正无穷的边。
每个员工点向汇点连一条容量为雇用所需费用的边。
答案为所有项目获利和减最小割。
“蕴含式最大获利问题”,套用最大权闭合子图的建模即可。
割掉源点向项目点的边即为放弃此项目。
割掉员工点向汇点的边即为雇用此员工。
项目点和员工点间的边不可割去。
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
const int dian=;
const int bian=;
const int INF=0x3f3f3f3f;
int ch[dian],h[dian],nxt[bian],ver[bian],val[bian],cr[dian];
int n,m,ans,aa,bb,tot;
int S,T;
void add(int a,int b,int c){
tot++;ver[tot]=b;nxt[tot]=h[a];val[tot]=c;h[a]=tot;
tot++;ver[tot]=a;nxt[tot]=h[b];val[tot]=;h[b]=tot;
}
bool tell(){
memset(ch,-,sizeof(ch));
queue<int>q;
q.push(S);
ch[S]=;
while(!q.empty()){
int t=q.front();
q.pop();
for(int i=h[t];i;i=nxt[i])
if(ch[ver[i]]==-&&val[i]){
q.push(ver[i]);
ch[ver[i]]=ch[t]+;
}
}
return ch[T]!=-;
}
int zeng(int a,int b){
if(a==T)
return b;
int r=;
for(int i=cr[a];i&&b>r;i=nxt[i])
if(ch[ver[i]]==ch[a]+&&val[i]){
int t=zeng(ver[i],min(b-r,val[i]));
val[i]-=t,r+=t,val[i^]+=t;
if(val[i])
cr[a]=i;
}
if(!r)
ch[a]=-;
return r;
}
int dinic(){
int r=,t;
while(tell()){
for(int i=;i<=n+m+;i++)
cr[i]=h[i];
while(t=zeng(S,INF))
r+=t;
}
return r;
}
int main(){
int cas;
scanf("%d",&cas);
while(cas--){
memset(nxt,,sizeof(nxt));
memset(h,,sizeof(h));
ans=;
tot=;
scanf("%d%d",&n,&m);
S=n+m+;
T=n+m+;
for(int i=;i<=n;i++){
scanf("%d",&aa);
ans+=aa;
add(S,i,aa);
}
for(int i=;i<=m;i++){
scanf("%d",&aa);
add(i+n,T,aa);
}
for(int i=;i<=n;i++){
scanf("%d",&aa);
for(int j=;j<=aa;j++){
scanf("%d",&bb);
add(i,bb++n,INF);
}
}
printf("%d\n",ans-dinic());
}
return ;
}