• 题解 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("
    ");
            }
    

    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!
    ");//判断是否有解
            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("
    ");
            }
        return 0;
        }
    
  • 相关阅读:
    关于Synchronized(一)
    关于Spring——事务(三)
    B2B、B2C、C2C、O2O、P2C、P2P
    《疯狂的程序员》读后有感
    祝贺拿到Offer
    软件测试中一般术语的英文和缩写
    笔试题目
    笔试题
    编码
    IO包中的其他类 打印流,序列流,操作对象,管道流,RandomAccessFile,操作基本数据类型,操作字节数组
  • 原文地址:https://www.cnblogs.com/Tony-Double-Sky/p/9285488.html
Copyright © 2020-2023  润新知