版权声明:Powered By Fighter https://blog.csdn.net/qq_30115697/article/details/88794864
题意:
思路
按照最大流的日常套路,我们来考虑如何建图。
显然我们把题的类型和每套试题分别作为图中的点。用编号为1~n的点表示n套试题,编号为n+1~n+k的点表示k种类型。
由于每套试题贡献为1,那么我们就从试题向它所属的所有类型连权值为1的边,从源点向试题连权值为1的边。然后,因为每种试题需要给定的 个,那么我们就从每种类型向汇点连权值为 的边,然后跑一遍最大流,那么我们就可以得到多套试题和一种类型的匹配。
如何判断是否有解呢?很简单,只需要判断最后得到的最大流量是否等于给定的 即可。
方案怎么输出呢?我们考虑当一套试题被选择时,当前边的流量一定已经被清空,而反向边一定不为0.所以我们可以枚举试题类型,遍历与这个类型直接相连的且边权不为0的点,就是选择的试题。
代码
#include <bits/stdc++.h>
#define MAXN 2005
#define MAXM 400005
#define INF 0x3f3f3f3f
using namespace std;
int n, m, k, cnt = 1, s, t;
int head[MAXN], vet[MAXM], Next[MAXM], cost[MAXM];
void add(int x, int y, int w){
cnt++;
Next[cnt] = head[x];
head[x] = cnt;
vet[cnt] = y;
cost[cnt] = w;
}
int dep[MAXN], vis[MAXN], cur[MAXN];
bool bfs(){
for(int i = 1; i <= t; i++){
dep[i] = INF;
vis[i] = 0;
cur[i] = head[i];
}
queue<int> q;
q.push(s);
vis[s] = true;
dep[s] = 1;
while(!q.empty()){
int x = q.front();
q.pop();
vis[x] = false;
for(int i = head[x]; i; i = Next[i]){
int v = vet[i];
if(cost[i] && dep[v] > dep[x]+1){
dep[v] = dep[x]+1;
if(!vis[v]){
vis[v] = true;
q.push(v);
}
}
}
}
return dep[t] != INF;
}
int ans;
int dfs(int x, int flow){
if(x == t){
ans += flow;
return flow;
}
int used = 0;
for(int i = head[x]; i; i = Next[i]){
int v = vet[i];
if(cost[i] && dep[v] == dep[x]+1){
int m = dfs(v, min(flow-used, cost[i]));
if(m){
cost[i] -= m;
cost[i^1] += m;
used += m;
if(used == flow) break;
}
}
}
return used;
}
void dinic(){
while(bfs()){
dfs(s, INF);
}
}
int main()
{
cin >> k >> n;
s = 0, t = n*2+1;
int p, x;
for(int i = 1; i <= k; i++){
scanf("%d", &x);
m += x;
add(i+n, t, x);
add(t, i+n, 0);
}
for(int i = 1; i <= n; i++){
add(s, i, 1);
add(i, s, 0);
scanf("%d", &p);
for(int j = 1; j <= p; j++){
scanf("%d", &x);
add(i, x+n, 1);
add(x+n, i, 0);
}
}
dinic();
if(ans != m){
puts("No Solution!");
return 0;
}
for(int i = 1; i <= k; i++){
printf("%d: ", i);
for(int j = head[i+n]; j; j = Next[j]){
int v = vet[j];
if(cost[j] && v <= n){
printf("%d ", v);
}
}
putchar('\n');
}
return 0;
}