732. [网络流24题] 试题库 二分图多重匹配/最大流
«问题描述:
假设一个试题库中有n道试题。每道试题都标明了所属类别。同一道题可能有多个类别属性。现要从题库中抽取m 道题组成试卷。并要求试卷包含指定类型的试题。试设计一个满足要求的组卷算法。
«编程任务:
对于给定的组卷要求,计算满足要求的组卷方案。
«数据输入:
由文件testlib.in提供输入数据。文件第1行有2个正整数k和n (2 <=k<= 20, k<=n<= 1000)k 表示题库中试题类型总数,n 表示题库中试题总数。第2 行有k 个正整数,第i 个正整数表示要选出的类型i 的题数。这k个数相加就是要选出的总题数m。接下来的n行给出了题库中每个试题的类型信息。每行的第1 个正整数p表明该题可以属于p类,接着的p个数是该题所属的类型号。
«结果输出:
程序运行结束时,将组卷方案输出到文件testlib.out 中。文件第i 行输出 “i:”后接类型i的题号。如果有多个满足要求的方案,只要输出1 个方案。如果问题无解,则输出“NoSolution!”。
输入文件示例
testlib.in
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
输出文件示例
testlib.out
1: 1 6 8
2: 7 9 10
3: 2 3 4 5
【问题分析】
二分图多重匹配问题,用最大流解决。
【建模方法】
建立二分图,每个类别为X集合中的顶点,每个题为Y集合中的顶点,增设附加源S和汇T。
1、从S向每个Xi连接一条容量为该类别所需数量的有向边。
2、从每个Yi向T连接一条容量为1的有向边。
3、如果一个题i属于一个类别j,连接一条从Xj到Yi容量为1的有向边。
求网络最大流,如果最大流量等于所有类别所需之和,则存在解,否则无解。对于每个类别,从X集合对应点出发的所有满流边,指向的B集合中的顶点就是该类别的所选的题(一个可行解)。
【建模分析】
二分图多重匹配问题。X,Y集合之间的边容量全部是1,保证两个点只能匹配一次,源汇的连边限制了每个点匹配的个数。求出网络最大流,如果流量等于X集合所有点与S边容量之和,那么则说明X集合每个点都有完备的多重匹配。
/* * Problem: 线性规划与网络流24题 #7 试题库问题 * Author: Guo Jiabao * Time: 2009.6.27 16:33 * State: Solved * Memo: 网络最大流 二分图多重匹配 */ #include <iostream> #include <cstdio> #include <cstdlib> #include <cmath> #include <cstring> using namespace std; const int MAXN=1030,MAXM=1000*20*2+MAXN*2,INF=~0U>>1; struct edge { edge *next,*op; int t,c; }*V[MAXN],*P[MAXN],ES[MAXM],*Stae[MAXN]; int N,M,S,T,EC,Ans,Maxflow,Total; int Lv[MAXN],Stap[MAXN]; inline void addedge(int a,int b,int c) { ES[++EC].next = V[a]; V[a]=ES+EC; V[a]->t=b; V[a]->c=c; ES[++EC].next = V[b]; V[b]=ES+EC; V[b]->t=a; V[b]->c=0; V[a]->op = V[b]; V[b]->op = V[a]; } void init() { int i,j,k,c; freopen("data.in","r",stdin); freopen("data.out","w",stdout); scanf("%d%d",&M,&N); S=0; T=N+M+1; for (i=1;i<=M;i++) { scanf("%d",&c); Total += c; addedge(S,i,c); } for (i=1;i<=N;i++) addedge(i+M,T,1); for (i=1;i<=N;i++) { scanf("%d",&k); for (j=1;j<=k;j++) { scanf("%d",&c); addedge(c,i+M,1); } } } bool Dinic_Label() { int head,tail,i,j; Stap[head=tail=0]=S; memset(Lv,-1,sizeof(Lv)); Lv[S]=0; while (head<=tail) { i=Stap[head++]; for (edge *e=V[i];e;e=e->next) { j=e->t; if (e->c && Lv[j]==-1) { Lv[j] = Lv[i]+1; if (j==T) return true; Stap[++tail] = j; } } } return false; } void Dinic_Augment() { int i,j,delta,Stop; for (i=S;i<=T;i++) P[i] = V[i]; Stap[Stop=1]=S; while (Stop) { i=Stap[Stop]; if (i!=T) { for (;P[i];P[i]=P[i]->next) if (P[i]->c && Lv[i] + 1 == Lv[j=P[i]->t]) break; if (P[i]) { Stap[++Stop] = j; Stae[Stop] = P[i]; } else Stop--,Lv[i]=-1; } else { delta = INF; for (i=Stop;i>=2;i--) if (Stae[i]->c < delta) delta = Stae[i]->c; Maxflow += delta; for (i=Stop;i>=2;i--) { Stae[i]->c -= delta; Stae[i]->op->c += delta; if (Stae[i]->c==0) Stop = i-1; } } } } void Dinic() { while (Dinic_Label()) Dinic_Augment(); } void print() { if (Total == Maxflow) { for (int i=1;i<=M;i++) { printf("%d: ",i); for (edge *e=V[i];e;e=e->next) if (e->c == 0 && e->t !=S) printf("%d ",e->t-M); putchar('\n'); } } else printf("No Solution!\n"); } int main() { init(); Dinic(); print(); return 0; }