这题跟前面两题很像,但也稍微有点不一样。因为都比较熟悉了,这里只简要提及建模的过程。
- 将试题作为左边的点,从源点向左边每个点连一条容量为 1 的边。(在最终的试卷中,每题充当一个类别的题目)
- 从左边的点向右边的点中,它可以属于的题目类别连一条容量为 1 的边。(同理)
- 将类别作为右边的点,从右边每个点向汇点连一条边,容量为该类别对应需要的题目数量。(注意在本题中对各类别题目的数量有严格限制,容量可以保证上限,即不会多。怎么保证不会少?)
之后跑一遍最大流即可。因为要求恰好出 m <script type="math/tex" id="MathJax-Element-1">m</script> 道题,且各类型要选出的题数之和就是要选出的总题数,因此判断是否满流就可以知道是否有解。如果有解,还要输出方案。跟圆桌问题有点不一样,这里不是根据左边的点来输出所到达的右边点,而是要输出对应每一个右边点,能到达它的左边点。其实也很好办,反其道而行之即可。对于右边的每个点,枚举其连向左边点的反向弧,若反向弧的反向弧(即原图中的正向边)残量为 0,则输出对应的左边点,即可。
参考代码:
//prog87试题库问题
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
using namespace std;
const int MAXK = 20 + 10;
const int MAXN = 1e3 + 10;
const int INF = 0x3f3f3f3f;
struct Edge {
Edge *next;
int cap;
int dest;
} edges[MAXK * MAXN], *current, *first_edge[MAXK + MAXN];
int n, k, s, t;
int need[MAXK];
bool vis[MAXK + MAXN];
Edge *counterpart(Edge *x) {
return edges + ((x - edges) ^ 1);
}
void insert(int u, int v, int c) {
current -> next = first_edge[u];
current -> cap = c;
current -> dest = v;
first_edge[u] = current ++;
}
int dfs(int u, int f) {
// printf("%d %d\n", u, f);
if (u == t) return f;
if (vis[u]) return 0; else vis[u] = true;
for (Edge *p = first_edge[u]; p; p = p -> next)
if (p -> cap)
if (int res = dfs(p -> dest, min(f, p -> cap))) {
p -> cap -= res;
counterpart(p) -> cap += res;
return res;
}
return 0;
}
int main(void) {
freopen("2208.in", "r", stdin);
freopen("2208.out", "w", stdout);
scanf("%d%d", &k, &n); current = edges;
s = 0; t = n + k + 1;
fill(first_edge, first_edge + t + 1, (Edge*)0);
int m = 0;
for (int i = 1; i <= k; i++) {
scanf("%d", &need[i]);
insert(i, t, need[i]); insert(t, i, 0);
}
for (int i = k + 1; i <= k + n; i++) {
int p; scanf("%d", &p);
insert(s, i, 1); insert(i, s, 0);
while (p--) {
int typ; scanf("%d", &typ);
insert(i, typ, 1); insert(typ, i, 0);
}
}
int ans = 0;
while (true) {
memset(vis, false, sizeof vis);
if (int res = dfs(s, INF)) ans += res; else break;
}
// printf("%d\n", ans);
if (ans < m) puts("No Solution!");
else {
for (int i = 1; i <= k; i++) {
int c = 0;
for (Edge *p = first_edge[i]; p; p = p -> next) c += (!counterpart(p) -> cap && p -> dest != t);
if (c < need[i]) { puts("No Solution!"); return 0; }
}
for (int i = 1; i <= k; i++) {
printf("%d:", i);
for (Edge *p = first_edge[i]; p; p = p -> next)
if (!counterpart(p) -> cap && p -> dest != t) printf(" %d", p -> dest - k);
putchar('\n');
}
}
return 0;
}
ps. 这题的 lemon_judge 是我写的,一开始出了点偏差,很惭愧。发现问题后及时作了修正。