题目链接:http://acm.fzu.edu.cn/problem.php?pid=2295
题目大意:Xzz要开始找工作,要做第 i 个工作需要有 mi 个前置技能,做完第 i 个工作可以获得 wi 个金币,同时有 k 对工作是无法同时做的,只能选择其中一个做。如果Xzz想要学习第 i 个技能的话需要花费 vi 个金币,同时要先学完ni个前置技能。现在问Xzz去学习技能和做部分工作最多能获得多少金币。
题目思路:我们可以将工作和技能都看成图上的点,工作所代表的点能获得金币,所以这个点的权值是正的;技能所代表的点需要花费金币,所以这个点的权值是负的。由于每个技能和工作都有前置条件,所以我们可以令前置技能往当前的工作或者技能连一条有向边。现在问题就转化为从图上任意一个入度为0的点(也就是不需要任何前置技能的点),走完整个图能获得的最大权值为多少。(现在先不考虑各个工作之间的限制条件)
根据样例二,我们可以建立如下的图
(n代表技能,m代表工作,后面的数字为技能编号,逗号后为权值)
这个题转化到这一步就是一个很经典的最大权闭合子图问题了。我们先建立一个超级源点S和一个超级汇点T,S向所有权值为正的点连一条流量为权值的边,所有权值为负的点向T连一条流量为权值的边。中间有前置关系的之间连一条流量为INF的边,再求一遍最小割,用所有正权值之和减去最小割的值,就是这个问题的答案了。
上图转化成的图为:
但本题还有一个限制条件,就是有些工作不能同时被选。处理这个条件,我们可以用2进制状压来枚举有限制条件的一对工作选哪一个,再重新建图,对每种情况求一次最小割,最后取最大的答案即可。比如样例2,就是建成如下两个图,分别跑最小割即可。
具体实现看代码:
#include <cstdio>
#include <cstring>
#include <queue>
#include <iostream>
#include <vector>
#include <algorithm>
#define fi first
#define se second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define pb push_back
#define MP make_pair
#define lowbit(x) x&-x
#define clr(a) memset(a,0,sizeof(a))
#define _INF(a) memset(a,0x3f,sizeof(a))
#define FIN freopen("in.txt","r",stdin)
#define IOS ios::sync_with_stdio(false)
#define debug(x) cout<<"["<<x<<"]"<<endl
using namespace std;
typedef long long LL;
typedef pair<int, int>pii;
typedef pair<LL, LL>pll;
const LL inf = 0x3f3f3f3f3f3f3f3f;
const int MX = 405;
const int MXE = 4 * MX * MX;
struct MaxFlow {
struct Edge {
int v, nxt;
LL w;
} edge[MXE];
int tot, num, s, t;
int head[MX];
void init() {
memset(head, -1, sizeof(head));
tot = 0;
}
void add(int u, int v, LL w) {
edge[tot].v = v;
edge[tot].w = w;
edge[tot].nxt = head[u];
head[u] = tot++;
edge[tot].v = u;
edge[tot].w = 0;
edge[tot].nxt = head[v];
head[v] = tot++;
}
int vis[MX];
LL d[MX], gap[MX];
void bfs() {
memset(d, 0, sizeof(d));
memset(gap, 0, sizeof(gap));
memset(vis, 0, sizeof(vis));
queue<int>q;
q.push(t);
vis[t] = 1;
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = head[u]; ~i; i = edge[i].nxt) {
int v = edge[i].v;
if (!vis[v]) {
d[v] = d[u] + 1;
gap[d[v]]++;
q.push(v);
vis[v] = 1;
}
}
}
}
int last[MX];
LL dfs(int u, LL f) {
if (u == t) return f;
LL sap = 0;
for (int i = last[u]; ~i; i = edge[i].nxt) {
int v = edge[i].v;
if (edge[i].w > 0 && d[u] == d[v] + 1) {
last[u] = i;
LL tmp = dfs(v, min(f - sap, edge[i].w));
edge[i].w -= tmp;
edge[i ^ 1].w += tmp;
sap += tmp;
if (sap == f) return sap;
}
}
if (d[s] >= num) return sap;
if (!(--gap[d[u]])) d[s] = num;
++gap[++d[u]];
last[u] = head[u];
return sap;
}
LL solve(int st, int ed, int n) {
LL flow = 0;
num = n;
s = st;
t = ed;
bfs();
memcpy(last, head, sizeof(head));
while (d[s] < num) flow += dfs(s, inf);
return flow;
}
} F;
int n, m, k, _;
pii a[6];
bool vis[MX];
struct node {
int val, n;
int p[MX];
} v[MX], w[MX];
int main() {
//FIN;
for (scanf("%d", &_); _; _--) {
scanf("%d%d%d", &n, &m, &k);
for (int i = 1; i <= n; i++) {
scanf("%d%d", &v[i].val, &v[i].n);
for (int j = 1; j <= v[i].n; j++) scanf("%d", &v[i].p[j]);
}
for (int i = 1; i <= m; i++) {
scanf("%d%d", &w[i].val, &w[i].n);
for (int j = 1; j <= w[i].n; j++) scanf("%d", &w[i].p[j]);
}
for (int i = 0; i < k; i++)
scanf("%d%d", &a[i].fi, &a[i].se);
int st = 0, ed = n + m + 1;
LL ans = 0;
for (int sta = 0; sta < (1 << k); sta++) {
clr(vis);
for (int j = 0; j < k; j++) {
if (vis[a[j].fi] == 1 && vis[a[j].se] == 1) continue;
if ((sta >> j) & 1) vis[a[j].se] = 1;
else vis[a[j].fi] = 1;
}
F.init();
LL sum = 0;
for (int i = 1; i <= m; i++) {
if (vis[i]) continue;
sum += w[i].val;
F.add(st, i, w[i].val);
for (int j = 1; j <= w[i].n; j++)
F.add(i, w[i].p[j] + m, inf);
}
for (int i = 1; i <= n; i++) {
F.add(i + m, ed, v[i].val);
for (int j = 1; j <= v[i].n; j++)
F.add(i + m, v[i].p[j] + m, inf);
}
LL flow = F.solve(st, ed, n + m + 2);
ans = max(ans, sum - flow);
}
cout << ans << endl;
}
return 0;
}