洛谷P2763 试题库问题

时间:2022-12-17 06:10:54

题目:https://www.luogu.org/problemnew/show/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!”。

 

输入输出样例

输入样例#1: 
3 15
3 3 4
2 1 2
1 3
1 3
1 3
1 3
3 1 2 3
2 2 3
2 1 3
1 2
1 2
2 1 2
2 1 3
2 1 2
1 1
3 1 2 3
输出样例#1: 
1: 1 6 8
2: 7 9 10
3: 2 3 4 5

说明

感谢 @PhoenixEclipse 提供spj

 

解析

网络流24题之一orz。

二分图多重匹配。

 

每道题跟T连容量为1的边。

每类题跟S连容量为需要该类题数量的边。

每道题跟它所在的题的类别连容量为1的边。

 

跑最大流,如果结果小于题目总数,则无解。

否则,对每条连接题的类别和题目的边检查,

若满流,输出题目的编号即可。

 

至于洛谷的90分:

检查一下是否有条边连了0节点(似乎数据有问题)。

洛谷P2763 试题库问题洛谷P2763 试题库问题
  1 #include<iostream>
2 #include<cstdio>
3 #include<algorithm>
4 #include<cstring>
5 #include<cmath>
6 #include<vector>
7 #include<queue>
8 using namespace std;
9 #define maxn 10000
10 struct line{
11 int to;
12 int cap,flow;
13 };
14 vector<line> edge;
15 vector<int> G[maxn];
16 bool vis[maxn];
17 int dis[maxn];
18 int cur[maxn];
19 int n,k,m,rep,re;
20 int inv[maxn];
21 int S,T;
22 void addedge(int from,int to,int cap){
23 edge.push_back((line){to,cap,0});
24 edge.push_back((line){from,0,0});
25 int sz=edge.size();
26 G[from].push_back(sz-2);
27 G[to].push_back(sz-1);
28 }
29 bool bfs(){
30 memset(vis,false,sizeof(vis));
31 vis[S]=true;
32 dis[S]=0;
33 queue<int> q;
34 q.push(S);
35 while (!q.empty()){
36 int u=q.front();
37 q.pop();
38 for (int i=0;i<G[u].size();++i){
39 line e=edge[G[u][i]];
40 if (vis[e.to]) continue;
41 if (e.flow>=e.cap) continue;
42 vis[e.to]=true;
43 dis[e.to]=dis[u]+1;
44 q.push(e.to);
45 }
46 }
47 return vis[T];
48 }
49 int dfs(int x,int a){
50 if (a==0||x==T) return a;
51 int f,flow=0;
52 for (int &i=cur[x];i<G[x].size();++i){
53 line &e=edge[G[x][i]];
54 if (dis[e.to]==dis[x]+1&&(f=dfs(e.to,min(a,e.cap-e.flow)))){
55 flow+=f;
56 a-=f;
57 e.flow+=f;
58 edge[G[x][i]^1].flow-=f;
59 if (!a) break;
60 }
61 }
62 return flow;
63 }
64 int dinic(){
65 int ans=0;
66 while (bfs()){
67 memset(cur,0,sizeof(cur));
68 ans+=dfs(S,10000);
69 }
70 return ans;
71 }
72 int main(){
73 scanf("%d%d",&k,&n);
74 S=0; T=2*n+1;
75 for (int i=1;i<=n;++i){
76 addedge(i+k,T,1);
77 }
78 for (int i=1;i<=k;++i){
79 scanf("%d",&inv[i]);
80 m+=inv[i];
81 addedge(S,i,inv[i]);
82 }
83 for (int i=1;i<=n;++i){
84 scanf("%d",&rep);
85 for (int j=1;j<=rep;++j){
86 scanf("%d",&re);
87 addedge(re,i+k,1);
88 }
89 }
90 if (dinic()<m){
91 printf("No Solution!");
92 return 0;
93 }
94 for (int i=1;i<=k;++i){
95 printf("%d:",i);
96 for (int j=0;j<G[i].size();++j){
97 line e=edge[G[i][j]];
98 if (e.cap==e.flow&&e.to) printf(" %d",e.to-k);
99 }
100 printf("\n");
101 }
102 return 0;
103 }
AC

 

90分,100分分别是这么写的:

洛谷P2763 试题库问题洛谷P2763 试题库问题
 1 //90分:
2 for (int i=1;i<=k;++i){
3 printf("%d:",i);
4 for (int j=0;j<G[i].size();++j){
5 line e=edge[G[i][j]];
6 if (e.cap==e.flow&&e.to!=k) printf(" %d",e.to-k);
7 }
8 printf("\n");
9 }
10 //100分:
11 for (int i=1;i<=k;++i){
12 printf("%d:",i);
13 for (int j=0;j<G[i].size();++j){
14 line e=edge[G[i][j]];
15 if (e.cap==e.flow&&e.to) printf(" %d",e.to-k);
16 }
17 printf("\n");
18 }
compare

希望有大佬给更好解释orz。