題目鏈接:
https://www.luogu.com.cn/problem/P1402
參考博客:
https://www.luogu.com.cn/blog/lhc/solution-p1402
算法:1:最大流FF算法
思路:
1:我們以房間、菜、人爲點建圖, 像這樣:注意,以下出現的所有邊邊權皆爲1,且其反向邊邊權爲0
2:S(=0)表示額外建的一個起始點,Ri(=i + n + n)表示第i個房間,Di(=i+n+n+p)表示第i種菜,由於人只有一個,而網絡流處理只經過一個點不方便,我們採用一種神奇方法——拆點,也就是說,把一個人看做兩個點,要匹配這個人必須經過這個人兩點之間的邊,這樣就可以控制這個人只匹配一次。如圖,Pi(=i)、Pi'(=i+n)表示第i個人
3:然後建邊,如圖,將S與所有Ri相連,將所有的Di與T相連,S作爲源點,T作爲匯點。如果Pi喜歡Rj,就將Rj與Pi相連。如果Pi喜歡Dj,就將Pi'與Dj之間相連。當然,Pi與Pi'之間也要連一條邊,然後就可以套網絡最大流FF算法
拓展:
1:有一天來了n批客人,每批客人喜歡的菜、房間都相同。第i批客人有gi位客人。其餘同原題
2:我們可以把每批客人當做2個點Pi、Pi',在Pi、Pi'之間連gi條邊連一條權爲gi的邊即可。菜、房間每種有多個同理
特別注意:
1:這類題目如果要用網絡最大流解決,一般來說,將“選擇者”放中間,並且要拆點,“被選物”放兩邊,直接與源點、匯點相連。但是這種做法“被選物”不能多於兩種
2:如果多於兩種,要怎麼做呢? 我也不知道
#include <bits/stdc++.h>
using namespace std;
const int maxn=4e2+2;
int n,p,q,a,s,t,tot=1,head[maxn],vis[maxn];
struct edge
{
int to,next,w;
}e[maxn<<7];
void addedge(int x,int y,int w)
{
e[++tot].to=y;
e[tot].w=w;
e[tot].next=head[x];
head[x]=tot;
}
int dfs(int x,int flow)
{
if(x==t)return flow;
vis[x]=1;
for(int i=head[x];i;i=e[i].next)
{
int y=e[i].to,w=e[i].w;
if(w&&!vis[y])
{
int t=dfs(y,min(flow,w));
if(t>0)
{
e[i].w-=t;
e[i^1].w+=t;
return t;
}
}
}
return 0;
}
int main()
{
ios::sync_with_stdio(0);
scanf("%d%d%d",&n,&p,&q);
s=0,t=p+n+n+q+1;
for(int i=1;i<=n;i++)addedge(i,i+n,1),addedge(i+n,i,0);
for(int i=2*n+1;i<=2*n+p;i++)addedge(0,i,1),addedge(i,0,0);
for(int i=2*n+p+1;i<=2*n+p+q;i++)addedge(i,t,1),addedge(t,i,0);
for(int i=1;i<=n;i++)for(int j=2*n+1;j<=2*n+p;j++)
{
scanf("%d",&a);
if(a)addedge(j,i,1),addedge(i,j,0);
}
for(int i=n+1;i<=2*n;i++)for(int j=2*n+p+1;j<=2*n+p+q;j++)
{
scanf("%d",&a);
if(a)addedge(i,j,1),addedge(j,i,0);
}
int res=0,ans=0;
while(memset(vis,0,sizeof(vis))&&(res=dfs(s,1e9))>0)ans+=res;
printf("%d\n",ans);
return 0;
}