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;
}