网络流。
原点向每个题目连边,容量为 $1$。
每个题目向对应的类型连边,容量为 $1$。
每个类型向汇点连边,容量为它所需的题目数量。
然后相当于求最小割,转化为最大流,于是做完了。
输出方案直接搜一下就好了。
#include <bits/stdc++.h>
using namespace std;
const int N = 1015, M = 2e4 + 15;
const long long INF = 0x3f3f3f3f;
int h[N], e[M], w[M], ne[M], idx = 0;
void add(int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
e[idx] = a, w[idx] = 0, ne[idx] = h[b], h[b] = idx++;
}
int n, m, S, T;
int d[N], now[N];
bool bfs() {
queue<int> q; q.push(S);
memset(d, 0, sizeof d);
d[S] = 1, now[S] = h[S];
while (q.size()) {
int u = q.front(); q.pop();
for (int i = h[u]; ~i; i = ne[i]) {
int v = e[i];
if (d[v] == 0 && w[i]) { //未被访问过且该边有残余容量
d[v] = d[u] + 1;
now[v] = h[v];
if (v == T) return true;
q.push(v);
}
}
}
return false;
}
long long find(int u, long long lim) {
if (u == T) return lim;
long long flow = 0;
for (int i = now[u]; ~i && flow < lim; i = ne[i]) {
now[u] = i; // 当前弧优化
int v = e[i];
if (d[v] == d[u] + 1 && w[i]) {
int x = find(v, min(w[i] * 1ll, lim - flow));
if (!x) d[v] = -1;
w[i] -= x, w[i ^ 1] += x, flow += x;
}
}
return flow;
}
long long dinic() {
long long ans = 0, res;
while (bfs()) {
while (res = find(S, INF)) ans += res;
}
return ans;
}
int sum = 0;
vector<int> ans[N];
int main() {
memset(h, -1, sizeof h);
scanf("%d%d", &m, &n);
S = 0, T = n + m + 1;
for (int i = 1; i <= n; i++) add(S, i, 1);
for (int i = 1, need; i <= m; i++) scanf("%d", &need), add(n + i, T, need), sum += need;
for (int i = 1; i <= n; i++) {
int k; scanf("%d", &k);
while (k--) {
int j; scanf("%d", &j);
add(i, n + j, 1);
}
}
if (dinic() != sum) return puts("No Solution!"), 0;
for (int u = 1; u <= n; u++) {
for (int i = h[u]; ~i; i = ne[i]) {
int v = e[i];
if (v > n && w[i] == 0) ans[v - n].push_back(u);
}
}
for (int i = 1; i <= m; i++) {
printf("%d: ", i);
for (int j : ans[i]) printf("%d ", j);
puts("");
}
return 0;
}