题解 P2763 【试题库问题】

P2763 试题库问题

题目描述

«问题描述:

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

«编程任务:

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

输入输出格式

输入格式:
第1行有2个正整数k和n (2 <=k<= 20, k<=n<= 1000)

k 表示题库中试题类型总数,n 表示题库中试题总数。第2 行有k 个正整数,第i 个正整数表示要选出的类型i的题数。这k个数相加就是要选出的总题数m。接下来的n行给出了题库中每个试题的类型信息。每行的第1 个正整数p表明该题可以属于p类,接着的p个数是该题所属的类型号。

输出格式:
第i 行输出 “i:”后接类型i的题号。如果有多个满足要求的方案,只要输出1个方案。如果问题无解,则输出“No Solution!”。


搞懂了这题,会对最大流有一个更透彻的理解

分析

首先,这题是一个与匹配有关的题,遇到有关匹配的题,我们可以先联想网络流

为什么呢?因为最大流的核心是限制,而匹配类题目经常和限制有关(一道题只能选一次,这就是限制),所以我们使用最大流进行求解

这题需要求的是匹配方案,可能刚开始会比较难理解,那么我们先解决 此问题是否有解这个子问题先

是否有解?

怎么知道是否有解呢?我们知道,可以求最大匹配数,若比要求的匹配数小,则无解。这实际上是一个二分图多重匹配问题,可以用最大流求解:

建模思路: 源点连一部,容量为要求的匹配数,按匹配条件连接一部和二部,容量为INF,二部连汇点,容量为要求匹配数,最大流即可求出匹配数。画图即可很快得到证明。

此题中,一部为试题,容量都为1(限制每题只能用一次),二部为题类,容量为所需题目数

选哪些题?

知道了有解,我们怎么求解方案呢?

回想一下最大流(我用的是Dinic)的运行过程,联系所学,我们知道:最大流有一个后悔机制,即更新一条弧的剩余容量是,同事更新反向边的剩余容量,代码表现在这:

int Dinic(int u,int flow){
    if(u == t)return flow;
    int rest = flow,k;
    for(int i = head[u];i;i = E[i].nxt){
        int v = E[i].v;
        if(E[i].dis && lev[v] == lev[u] + 1 && rest){
            k = Dinic(v,min(rest,E[i].dis));
            if(!k)lev[v] = 0;
            E[i].dis -= k;//这这这
            E[i ^ 1].dis += k;//还有这
            rest -= k;
            }
        }
    return flow - rest;
    }

我们可以利用这一点求方案

试想,如果试题库 \(S\) 中有一题 \(R\) ,那么最大流一定从这\(R-->S\)这条弧走过过,换言之,这条弧一定对最大流量有所贡献

既然正边有所贡献,那么依据后悔机制,其反边容量不就不为0了吗?

我们从试题库出发,遍历所有连\(T\)的反边(连向题目),若某边不为0,则被连的题目一定被此试题库选中

    for(int u = 1 + numl;u <= numr + numl;u++){//所有试题库
        printf("%d:",u - numl);
        for(int i = head[u];i;i = E[i].nxt){
            int v = E[i].v;
            if(v == t)continue;//不能访问汇点
            if(E[i].dis == 1){//对最大流量有贡献
                printf("%d ",v);
                need[u - numl]--;
                if(!need[u - numl])break;//题目够了
                }
            }
        printf("\n");
        }

AC代码

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
int RD(){
    int out = 0,flag = 1;char c = getchar();
    while(c < '0' || c >'9'){if(c == '-')flag = -1;c = getchar();}
    while(c >= '0' && c <= '9'){out = out * 10 + c - '0';c = getchar();}
    return flag * out;
    }
const int maxn = 10019,INF = 1e9;
int numr,numl,tot,nume = 1;
int need[maxn];
int s,t,maxflow;
int head[maxn];
struct Node{
    int v,dis,nxt;
    }E[maxn << 2];
void add(int u,int v,int dis){
    E[++nume].nxt = head[u];
    E[nume].v = v;
    E[nume].dis = dis;
    head[u] = nume;
    }
int lev[maxn];
bool bfs(){
    queue<int>Q;
    memset(lev,0,sizeof(lev));
    lev[s] = 1;
    Q.push(s);
    while(!Q.empty()){
        int u = Q.front();Q.pop();
        for(int i = head[u];i;i = E[i].nxt){
            int v = E[i].v;
            if(E[i].dis && !lev[v]){
                lev[v] = lev[u] + 1;
                if(v == t)return 1;
                Q.push(v);
                }
            }
        }
    return 0;
    }
int Dinic(int u,int flow){
    if(u == t)return flow;
    int rest = flow,k;
    for(int i = head[u];i;i = E[i].nxt){
        int v = E[i].v;
        if(E[i].dis && lev[v] == lev[u] + 1 && rest){
            k = Dinic(v,min(rest,E[i].dis));
            if(!k)lev[v] = 0;
            E[i].dis -= k;
            E[i ^ 1].dis += k;
            rest -= k;
            }
        }
    return flow - rest;
    }
int main(){
    numr = RD();numl = RD();//左部为题目。右部为题类,右部i为从numl + i
    s = numr + numl + 1,t = numr + numl + 2;
    int temp;
    for(int i = numl + 1;i <= numl + numr;i++){
        temp = RD();
        need[i - numl] = temp;
        tot += temp;
        add(i,t,temp);
        add(t,i,0);
        }
    int num;
    for(int i = 1;i <= numl;i++){
        num = RD();
        add(s,i,1);
        add(i,s,0);
        for(int j = 1;j <= num;j++){
            temp = RD();
            add(i,numl + temp,1);
            add(numl + temp,i,0);//建图
            }
        }
    int flow = 0;
    while(bfs()){
        while(flow = Dinic(s,INF))maxflow += flow;
        }
    if(maxflow < tot){
        printf("No Solution!\n");//判断是否有解
        return 0;
        }
    for(int u = 1 + numl;u <= numr + numl;u++){
        printf("%d:",u - numl);
        for(int i = head[u];i;i = E[i].nxt){
            int v = E[i].v;
            if(v == t)continue;//不能访问汇点
            if(E[i].dis == 1){//对最大流量有贡献
                printf("%d ",v);
                need[u - numl]--;
                if(!need[u - numl])break;//题目够了
                }
            }
        printf("\n");
        }
    return 0;
    }

转载于:https://www.cnblogs.com/Tony-Double-Sky/p/9285488.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值