洛谷 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
網路流
網路流是圖論中的一種理論與方法,研究網路上的一類最優化問題 。1955年 ,T.E.哈里斯在研究鐵路最大通量時首先提出在一個給定的網路上尋求兩點間最大運輸量的問題。1956年,L.R. 福特和 D.R. 富爾克森等人給出瞭解決這類問題的演算法,從而建立了網路流理論。所謂網路或容量網路指的是一個連通的賦權有向圖 D= (V、E、C) , 其中V 是該圖的頂點集,E是有向邊(即弧)集,C是弧上的容量。此外頂點集中包括一個起點和一個終點。網路上的流就是由起點流向終點的可行流,這是定義在網路上的非負函式,它一方面受到容量的限制,另一方面除去起點和終點以外,在所有中途點要求保持流入量和流出量是平衡的。如果把下圖看作一個公路網,頂點v1…v6表示6座城鎮,每條邊上的權數表示兩城鎮間的公路長度。現在要問 :若從起點v1將物資運送到終點v6去 ,應選擇那條路線才能使總運輸距離最短?這樣一類問題稱為最短路問題 。 如果把上圖看作一個輸油管道網 , v1 表示傳送點,v6表示接收點,其他點表示中轉站 ,各邊的權數表示該段管道的最大輸送量。現在要問怎樣安排輸油線路才能使從v1到v6的總運輸量為最大。這樣的問題稱為最大流問題。 ————《百度百科》
沒有接觸過網路流的朋友可以去看下這篇部落格:
https://www.cnblogs.com/SYCstudio/p/7260613.html
我們進入正題!
“網路流是我造詣最深的幾個演算法之一,在我的巔峰時期,只要是網路流的題給我看幾分鐘我就能秒。而秒掉網路流的關鍵所在就是建圖。”
————PB
現在我們有N道題,K個類別。而同一道題有Pi個類別屬性。我們被要求選出M道題並符合條件(每個型別選Ti道題)。
顯然這是一道類似二分圖匹配的題,每個題目對應一些類別,而我們要求最大的一個能滿足M個匹配的方案。
所以直接簡圖,跑一遍最大流,如果最大流等於M,輸出方案。
接下來是建圖的問題,理所當然的,每道題只能選一次,所以從超級原點S,連一條1的邊到每道題。
每道題只能併入一個型別中,所以從每道題連Pi條1的邊到這道題所對應的型別。
因為第i個型別的題要選Ti道,所以從每個型別的點連一條為Ti 的邊到超級匯點T(T=K+N+1)。
(沒有標出的邊權值都為1,這個圖和樣例無關)
建完圖後,直接跑網路流就好了。判斷最大流是否等於題目數。
剩下的是要輸出路徑。
通常來說,網路流輸出路徑只需要找到殘量為0的邊跑DFS,設個bool陣列判斷是否走過就好了。
由於有SPJ,所以也沒有什麼限制,路徑可以跑的很隨意。 下面放出程式碼,程式碼中不會有太多的註釋。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=2001;
const int INF=104859600;
struct edge//存圖
{
int node,w,next;
}h[MAXN*10];
int Head[MAXN],Depth[MAXN],Stack[MAXN];//Head表示鄰接表的頭指標,Depth表示分層圖深度。
int k,n,m,p,tot=-1,x,y,kind,sum=0,s,t,top,ans=0;
bool visit[MAXN];
inline void add(int u,int v,int w)//加邊
{
h[++tot].next=Head[u];
h[tot].node=v;
h[tot].w=w;
Head[u]=tot;
}
inline bool bfs()//跑BFS將圖分層
{
int q[MAXN*10],head=1,tail=1;
memset(Depth,0,sizeof(Depth));
Depth[s]=1;
q[head]=s;
while(head<=tail)
{
int x=q[head++];
for(register int i=Head[x];i!=-1;i=h[i].next)
{
int v=h[i].node;
if(h[i].w>0&&Depth[v]==0)
{
Depth[v]=Depth[x]+1;
q[++tail]=v;
}
}
}
return Depth[t];
}
inline int dfs(int x,int dist)//跑DFS獲取最大流
{
if(x==t)
return dist;
for(register int i=Head[x];i!=-1;i=h[i].next)
{
int v=h[i].node;
if(h[i].w>0&&Depth[v]==Depth[x]+1)
{
int di=dfs(v,min(dist,h[i].w));
if(di>0)
{
h[i].w-=di;
h[i^1].w+=di;
return di;
}
}
}
return 0;
}
inline void run(int x)//輸出路徑
{
for(register int i=Head[x];i!=-1;i=h[i].next)
{
int v=h[i].node;
if(h[i].w==0&&visit[v]==0)
{
Stack[++top]=v-k;
visit[v]=1;
}
}
}
int main()
{
memset(Head,-1,sizeof(Head));
scanf("%d %d",&k,&n);
s=0,t=n+k+1;
for(register int i=1;i<=k;i++)
{
scanf("%d",&kind);
sum+=kind;
add(s,i,kind);
add(i,s,0); //這裡和上面講的有點不一樣。
} //為了方便除錯(樣例型別少)我把原點和型別連線把題目和匯點連線
//實際上是沒有區別的
for(register int i=1;i<=n;i++)
{
add(i+k,t,1);
add(t,i+k,0);
scanf("%d",&p);
for(register int j=1;j<=p;j++)
{
scanf("%d",&y);
add(y,i+k,1);
add(i+k,y,0);
}
}
while(bfs())
ans+=dfs(s,INF);
if(ans!=sum)
puts("No Solution!");
else
{
for(register int i=1;i<=k;i++)
{
top=0;
run(i);
printf("%d: ",i);
for(int j=top;j>=1;j--)
printf("%d ",Stack[j]);
puts("");
}
}
return 0;
}