- 在
OI
中,隨着考查圖論的難度越來越大,越來越多的圖論題的難度並不在算法身上,而在如何轉化原題目而可以方便而快速地使用圖論知識解決上。 - 作爲圖論中的一個重要分支,網絡流的考查自然是
OI
的重點,不可避免地,考查網絡流的題目也和其它圖論題目一樣,題意變得難懂了,建模變得困難了。 - 故而,掌握一些建圖的技巧具有非常重要的意義。
我們發現,我們必然需要從顧客分別連一條邊到房間和菜,然後跑最大流。但是,我們發現,如果按普通的算法的話,我們很難直接用最大流算法解決本題。因爲你不好維護一個顧客是否既分到了喜歡的房間又吃到了喜歡的菜。
仔細發現可以發現,我們可以把表示顧客點放到中間,把表示房間的點放到左邊,把表示菜的點放到右邊,這樣,我們就可以把全部信息在一張圖上完整而方便的表示出來了。下面是一個可能的建好的圖:
當然,爲了方便處理,我們需要將所有的房間向一個超級源 連容量爲 的邊,所有的菜向一個超級匯 連容量爲 的邊。
但是,這樣是可能會錯的,比如這樣(圖片爲原創,B
即房間,A
即顧客,C
即菜):
很明顯,答案應該爲 ,但該圖的最大流爲 。
怎麼辦,很簡單,我們把一個顧客變成兩個人,一個自己向另一個自己連一條容量爲 的邊。這樣就搞定了。
const int N=410,M=3e4+100;
struct edge{
int next,to,dis;
}e[M<<1];int h[N],tot=1;
inline void add(int a,int b,int c){
e[++tot]=(edge){h[a],b,c};h[a]=tot;
e[++tot]=(edge){h[b],a,0};h[b]=tot;
}
int s,t,dep[N],cur[N],n,m,p,ans;
inline bool bfs_init(int s){
memset(dep,-1,sizeof(dep));
dep[s]=1;queue<int> q;q.push(s);
while (q.size()){
int u=q.front(),i;q.pop();
for(i=h[u];i;i=e[i].next){
register int to=e[i].to;
if (dep[to]==-1&&e[i].dis>0){
dep[to]=dep[u]+1;
q.push(to);
}
}
}
return dep[t]!=-1;
}
int dfs(int u,int dist){
if (u==t) return dist;//遞歸計算邊界條件
register int flow=0;//可以得到的最大流量
for(int &i=cur[u];i;i=e[i].next)
if (e[i].dis>0){//有計算的意義
register int d,to=e[i].to;
if (dep[to]==dep[u]+1){//符合限制條件
if ((d=dfs(to,min(dist,e[i].dis)))>0){
e[i].dis-=d;e[i^1].dis+=d;flow+=d;dist-=d;
if (dist==0) return flow;//無剩餘流量,返回
}
}
}
if (dist) dep[u]=-1;
return flow;
}
const int inf=0x3f3f3f3f;
inline void dinic_algorithm(){
register int tmp;//臨時儲存變量
while (bfs_init(s)){//分層成功
memcpy(cur,h,sizeof(h));
while ((tmp=dfs(s,inf))>0)
ans+=tmp;//累計貢獻
}
printf("%d",ans);
}
int main(){
scanf("%d%d%d",&n,&m,&p);
s=2*n+m+p+1;t=s+1;//起始點
for(int i=1;i<=n;i++)
for(int j=1,x;j<=m;j++){
scanf("%d",&x);//輸入
if (x==1) add(j+n,i,1);
}
for(int i=1;i<=n;i++)
for(int j=1,x;j<=p;j++){
scanf("%d",&x);//注意編號的不同
if (x==1) add(i+n+m+p,j+n+m,1);
}
for(int i=1;i<=m;i++) add(s,i+n,1);
for(int i=1;i<=p;i++) add(i+n+m,t,1);
for(int i=1;i<=n;i++) add(i,i+n+m+p,1);
dinic_algorithm();//調用dinic算法求解
return 0;//Goodbye,my dear program
}