Libre 6006 「网络流 24 题」试题库 / Luogu 2763 试题库问题 (网络流,最大流)
Description
问题描述:
假设一个试题库中有n道试题。每道试题都标明了所属类别。同一道题可能有多个类别属性。现要从题库中抽取m 道题组成试卷。并要求试卷包含指定类型的试题。试设计一个满足要求的组卷算法。
编程任务:
对于给定的组卷要求,计算满足要求的组卷方案。
Input
第1行有2个正整数k和n (2 <=k<= 20, k<=n<= 1000)
k 表示题库中试题类型总数,n 表示题库中试题总数。第2 行有k 个正整数,第i 个正整数表示要选出的类型i的题数。这k个数相加就是要选出的总题数m。接下来的n行给出了题库中每个试题的类型信息。每行的第1 个正整数p表明该题可以属于p类,接着的p个数是该题所属的类型号。
Output
第i 行输出 “i:”后接类型i的题号。如果有多个满足要求的方案,只要输出1个方案。如果问题无解,则输出“No Solution!”。
Sample Input
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
Sample Output
1: 1 6 8
2: 7 9 10
3: 2 3 4 5
Http
Libre:https://loj.ac/problem/6006
Luogu:https://www.luogu.org/problem/show?pid=2763
Source
网络流,最大流
解决思路
题目的描述有些问题,应该是一道题目如果选择了作为这种类型就不能选择为另外一种类型了。
对于每一种类型的要求,从源点连一条容量为要求数目的边到类型;对于每一道试题,连从对应的类型到试题容量为1的边,同时从试题到汇点连一条容量为1的边。这样就可以保证一个试题只能选择作为一种类型。然后跑最大流即可。
若最大流等于所有类型要求数量之和(即从源点出发的所有边满流),则说明有解,否则无解。
另:这里使用Dinic求解最大流,可以参考我的这篇文章
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxN=2000;
const int maxM=maxN*maxN;
const int inf=2147483647;
class Edge
{
public:
int u,v,flow;
};
int n,m;
int cnt=-1;
int Head[maxN];
int Next[maxM];
Edge E[maxM];
int depth[maxN];
int cur[maxN];
int Q[maxN];
void Add_Edge(int u,int v,int flow);
bool bfs();
int dfs(int u,int flow);
int main()
{
memset(Head,-1,sizeof(Head));
int sum=0;
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
{
int num;
scanf("%d",&num);
sum+=num;//统计总类型要求数量
Add_Edge(0,i,num);//连边源点到类型
}
for (int i=1;i<=m;i++)
{
int p;
scanf("%d",&p);
for (int j=1;j<=p;j++)
{
int u;
scanf("%d",&u);
Add_Edge(u,n+i,1);//连边类型到题目
}
Add_Edge(n+i,n+m+1,1);//连边题目到汇点
}
int Ans=0;//求解最大流
while (bfs())
{
for (int i=0;i<=n+m+1;i++)
cur[i]=Head[i];
while (int di=dfs(0,inf))
Ans+=di;
}
if (Ans<sum)//若最大流小于要求总数,则说明无解
{
cout<<"No Solution!"<<endl;
return 0;
}
for (int i=1;i<=n;i++)//有解,则输出一种方案
{
cout<<i<<": ";
for (int j=Head[i];j!=-1;j=Next[j])
{
int v=E[j].v;
if ((v>=n+1)&&(v<=n+m)&&(E[j].flow==0))
cout<<v-n<<" ";
}
cout<<endl;
}
return 0;
}
void Add_Edge(int u,int v,int flow)
{
cnt++;
Next[cnt]=Head[u];
Head[u]=cnt;
E[cnt].u=u;
E[cnt].v=v;
E[cnt].flow=flow;
cnt++;
Next[cnt]=Head[v];
Head[v]=cnt;
E[cnt].u=v;
E[cnt].v=u;
E[cnt].flow=0;
}
bool bfs()
{
memset(depth,-1,sizeof(depth));
int h=1,t=0;
Q[1]=0;
depth[0]=1;
do
{
t++;
int u=Q[t];
//cout<<u<<endl;
for (int i=Head[u];i!=-1;i=Next[i])
{
int v=E[i].v;
//cout<<v<<endl;
if ((depth[v]==-1)&&(E[i].flow>0))
{
depth[v]=depth[u]+1;
h++;
Q[h]=v;
}
}
}
while (h!=t);
if (depth[n+m+1]==-1)
return 0;
return 1;
}
int dfs(int u,int flow)
{
if (u==n+m+1)
return flow;
for (int &i=cur[u];i!=-1;i=Next[i])
{
int v=E[i].v;
if ((depth[v]==depth[u]+1)&&(E[i].flow>0))
{
int di=dfs(v,min(flow,E[i].flow));
if (di>0)
{
E[i].flow-=di;
E[i^1].flow+=di;
return di;
}
}
}
return 0;
}