题目: LINK
题意:有m个项目要做,有n个工人,每个项目要且必须要这些工人中的一个子集来完成, 一个工人可以参与多个项目,给出每个项目完成后的收益gi和雇佣每个工人的花费ci,以及每个项目必须需要的工人的编号,问能获得的最大收益是多少。
闭合图的概念:在一个图中,我们选取一些点构成集合,记为V,且集合中的出边(即集合中的点的向外连出的弧),所指向的终点(弧头)也在V中,则我们称V为闭合图。
最大权闭合图即在所有闭合图中,集合中点的权值之和最大的V,我们称V为最大权闭合图。
建图:设立源点S,汇点T。s到每个项目连边容量为项目的收益,每个工人到T连边容量为工人的花费,对于每个项目需要的工人之间连边,容量为无穷大.
可以求得最小割(最大流)[S,T]; 最终要求的结果为sum - [S,T]. (sum为所有项目收益的和)
为什么要这么做呢?
对于建立的图的每一个割,都会对应一个闭合图,即[S,T]分割成的包含S的集合,这个闭合图就是包含S的部分。
[S,T]中的边要么是与S相连,要么是和T相连,因为其他边的容量都是INF。
结果ans = A' - B' (A'不在最小割中的项目的和, B'是在最小割中的工人的花费和);
[S, T] = A - A' + B' (A是所有项目的收益和);
所以ans = A - [S,T];
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
#include <cmath>
#include <queue>
#include <map>
#include <set>
using namespace std;
#define INF 1000000000
//typedef __int64 LL;
#define N 110
#define M 222
int t, n, m, a[N], b[N], tot, hh[M], dis[M], lev[M], S, T;
struct node {
int u, v, w, next;
}edge[100001];
void init() {
memset(hh, -1, sizeof(hh));
tot = 0;
}
void add(int u, int v, int w){
edge[tot].u = u; edge[tot].v = v;
edge[tot].w = w; edge[tot].next = hh[u];
hh[u] = tot ++;
}
int bfs() {
queue<int > Q;
memset(lev, -1, sizeof(lev));
Q.push(S); lev[S] = 0;
while(!Q.empty()) {
int u = Q.front(); Q.pop();
for(int i = hh[u]; i != -1; i = edge[i].next) {
int v = edge[i].v;
if(edge[i].w && lev[v] == -1) {
lev[v] = lev[u] + 1;
Q.push(v);
}
}
}
return lev[T] != -1;
}
int dfs(int u, int flow) {
if(u == T) return flow;
int tmp = flow, ad;
for(int i = hh[u]; i != -1; i = edge[i].next) {
int v = edge[i].v;
if(lev[v] == lev[u] + 1 && tmp > 0 && edge[i].w) {
ad = dfs(v, min(tmp, edge[i].w));
if(!tmp) break;
edge[i].w -= ad;
edge[i^1].w += ad;
tmp -= ad;
}
}
if(ad == 0) lev[u] = -1;
return flow- tmp;
}
int dinic() {
int ret = 0, tmp;
while(bfs()) while(tmp = dfs(S, INF)) ret += tmp;
return ret;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
#endif // ONLINE_JUDGE
scanf("%d", &t);
while(t -- ) {
scanf("%d%d", &m, &n);
S = n + m + 1;
T = n + m + 2;
int tmp , sum = 0, gg;
init();
for(int i = 0; i < m; i++) {
scanf("%d", &a[i]);
sum += a[i] ;
add(S, i, a[i]); add(i, S, 0);
}
for(int i = 0; i < n; i++) {
scanf("%d", &b[i]);
add(i+m, T, b[i]); add(T, i+m, 0);
}
for(int i = 0; i < m; i++) {
scanf("%d", &tmp);
for(int j = 1; j <= tmp; j++) { scanf("%d", &gg);
add(i, gg+m, INF); add(gg+m, i, 0);
}
}
int ans = dinic();
printf("%d\n", sum - ans);
}
return 0;
}