洛谷P2763 试题库问题

题目: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节点(似乎数据有问题)。

  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分分别是这么写的:

 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。

posted @ 2017-12-17 21:03  lonlyn  阅读(352)  评论(0编辑  收藏  举报