- 在
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
}