P2763试题库问题(二分图匹配+匈牙利/dinic)

传送门

题目描述:

问题描述:

假设一个试题库中有 n 道试题。每道试题都标明了所属类别。同一道题可能有多个类别属性。现要从题库中抽取 m 道题组成试卷。并要求试卷包含指定类型的试题。试设计一个满足要求的组卷算法。

编程任务:

对于给定的组卷要求,计算满足要求的组卷方案。

输入格式

第一行有两个正整数 k 和 nk 表示题库中试题类型总数,n 表示题库中试题总数。

第二行有 k个正整数,第 ii 个正整数表示要选出的类型 i 的题数。这 k 个数相加就是要选出的总题数 m

接下来的 n 行给出了题库中每个试题的类型信息。每行的第一个正整数 p 表明该题可以属于 p 类,接着的 p 个数是该题所属的类型号。

思路:二分图匹配,不过这次匹配的个数不是一个,而是多个,而且还要输出结果,还是照搬匈牙利

算法,匹配满了就从头再找替换点。用dinic做二分图也可以,我们源点对试题连边,容量为1,试题再对

其所属类型连边,容量为1,类型再对汇点连边,容量为类型所需试题数,跑最大流,如果满流(结果与所有类型所需试题和想等),则满足

打印结果的话,我们就对所有类型的边进行判断,排除掉汇点,那么其余边就是与试题相连的反向边,如果反向边有流量,说明有流量

从试题流过来,即选择了该试题。

匈牙利代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100005;
const int inf = 0x3f3f3f3f;
struct edge {
    int f, t, nxt;
}e[maxn];
int hd[maxn], tot = 1;
void add(int f, int t) {
    e[++tot] = { f,t,hd[f] };
    hd[f] = tot;
}
int now[maxn], match[30][maxn], need[maxn];
bool vis[maxn];
bool dfs(int u) {
    for (int i = hd[u]; i; i = e[i].nxt) {
        int v = e[i].t;
        if (vis[v])continue;
        vis[v] = 1;
        if (now[v]!=need[v]) {//如果还没满,就直接放
            now[v]++;
            match[v][now[v]] = u;
            return true;
        }
        for (int j = 1; j <= now[v]; j++) {//找替换点
            if (dfs(match[v][j])) {
                match[v][j] = u;
                return true;
            }
        }
    }
    return false;
}
int k, n;
int main() {
    //freopen("test.txt", "r", stdin);
    scanf("%d%d", &k, &n);
    for (int i = 1; i <= k; i++) {
        scanf("%d", &need[i]);
    }
    for (int i = 1; i <= n; i++) {
        int num; scanf("%d", &num);
        for (int j = 1; j <= num; j++) {
            int p; scanf("%d", &p);
            add(i, p);
        }
    }
    for (int i = 1; i <= n; i++) {
        memset(vis, 0, sizeof(vis));
        dfs(i);
    }
    bool f = 1;
    for (int i = 1; i <= k; i++) {//判断是否全部放满
        if (now[i] != need[i]) {
            f = 0; break;
        }
    }
    if (f) {//打印结果
        for (int i = 1; i <= k; i++) {
            printf("%d:", i);
            for (int j = 1; j <= need[i]; j++) {
                printf(" %d", match[i][j]);
            }
            printf("\n");
        }
    }
    else {
        printf("No Solution!\n");
    }
    return 0;
}

 dinic部分代码:

int k, n;
int need[maxn];
int main() {
    //freopen("test.txt", "r", stdin);
    scanf("%d%d", &k, &n);
    s = n + k + 1, t = s + 1;
    ll sum = 0;
    for (int i = 1; i <= k; i++) {
        int need; scanf("%d", &need);
        sum += need;
        add(i + n, t, need);
    }
    for (int i = 1; i <= n; i++) {
        add(s, i, 1);
        int num; scanf("%d", &num);
        for (int j = 1; j <= num; j++) {
            int p; scanf("%d", &p);
            add(i, p+n, 1);
        }
    }
    ll ans = dinic();
    if (ans != sum) {//没有满流,不能匹配成功
        printf("No Solution!\n");
    }
    else {
        for (int u = 1+n; u <= k+n; u++) {//对类型的连边判断
            printf("%d:", u - n);
            for (int i = hd[u]; i; i = e[i].nxt) {
                int v = e[i].t;
                if (v == t)continue;//排除汇点
                if (e[i].flow > 0) {//反向边有流量回去,说明有流量从它那过来
                    printf(" %d", v);
                }
            }
            printf("\n");
        }
    }
    return 0;
}

 

posted @ 2021-04-13 08:41  cono奇犽哒  阅读(55)  评论(0)    收藏  举报