[SMOJ2208]试题库问题

97 篇文章 0 订阅
10 篇文章 0 订阅

这题跟前面两题很像,但也稍微有点不一样。因为都比较熟悉了,这里只简要提及建模的过程。

  • 将试题作为左边的点,从源点向左边每个点连一条容量为 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 是我写的,一开始出了点偏差,很惭愧。发现问题后及时作了修正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值