P4313 文理分科
这篇博客是文理分科的题解暨最大流建模思路的套路整理。
很多人看到最大流的题就说是最小割板子。那最小割到底有什么意义?我被这个问题困惑了很久。
我们到底要去割一个怎样的图?我们为什么要去割这个图?
做了一些习题之后,我大概发现了如下的规律:
源点和汇点表示两个矛盾的状态,最小割的过程即是找使状态合法的最小代价。
形象化地,我们以本题为例。
显然题中的人不可能既选文科又选理科(不排除现实生活中有这样的神仙)。
则我们将最终和源点\(S\)相连的点认为是选择文科,理科同理。
考虑如何连边:
每个点向源点连一条价值为他选择文科的满意值,汇点同理。
若连向源点的边被割断,则代表他选择理科,反之亦然。
发现这样建图一个人的文科和理科总有一个会被割掉,符合题意。
对于\(same_{art}[i][j]\),新建节点\(cnt\),考虑它的要求。
若要满足全为文科,则所有理科的边都要割掉,否则我们也可以割掉这一条边。
故连\(S\)到\(cnt\)价值为\(same_{art}[i][j]\)的边,再从\(cnt\)向\((i,j)\)和其相邻点连价值为\(inf\)的边。
对于理科的\(same_{science}[i][j]\)是一样的道理。
我们发现将\(S\)和\(T\)具象化以后,每一条连边都是有意义的,这样更有利于解题。
代码如下,仅供参考(本人码风就那样,多多体谅):
#include<bits/stdc++.h>
using namespace std;
#define inf 1e9
const int N=105;
const int maxn=3e5+10;
int n,m,ans,t,cnt;
int beg[maxn],nex[maxn],to[maxn],w[maxn],e;
inline void add(int x,int y,int z){
nex[e]=beg[x];beg[x]=e;
to[e]=y;w[e]=z;e++;
}
int dep[maxn];queue<int>q;
inline int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
return x*f;
}
inline int id(int x,int y){return (x-1)*m+y;}
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
inline int bfs(){
memset(dep,0,sizeof(dep));
dep[0]=1;q.push(0);
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=beg[x];~i;i=nex[i])
if(w[i]&&!dep[to[i]]){
dep[to[i]]=dep[x]+1;
q.push(to[i]);
}
}
return dep[t]!=0;
}
inline int dfs(int x,int lim){
if(x==t||!lim)return lim;
int res=0;
for(int i=beg[x];~i;i=nex[i])
if(w[i]&&dep[to[i]]==dep[x]+1){
int f=dfs(to[i],min(w[i],lim));
if(!f){dep[to[i]]=-1;continue;}
lim-=f;res+=f;
w[i]-=f;w[i^1]+=f;
}
return res;
}
int main(){
n=read(),m=read();
memset(beg,-1,sizeof(beg));
int val;t=cnt=n*m+1;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
val=read();ans+=val;
add(0,id(i,j),val);add(id(i,j),0,0);
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
val=read();ans+=val;
add(id(i,j),t,val);add(t,id(i,j),0);
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
cnt++;val=read();ans+=val;
add(0,cnt,val);add(cnt,0,0);
add(cnt,id(i,j),inf);add(id(i,j),cnt,0);
for(int k=0;k<4;k++){
int x=i+dx[k];int y=j+dy[k];
if(x<1||y<1||x>n||y>m)continue;
add(cnt,id(x,y),inf);add(id(x,y),cnt,0);
}
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
cnt++;val=read();ans+=val;
add(cnt,t,val);add(t,cnt,0);
add(id(i,j),cnt,inf);add(cnt,id(i,j),0);
for(int k=0;k<4;k++){
int x=i+dx[k];int y=j+dy[k];
if(x<1||y<1||x>n||y>m)continue;
add(id(x,y),cnt,inf);add(cnt,id(x,y),0);
}
}
while(bfs())ans-=dfs(0,inf);
printf("%d\n",ans);
return 0;
}
深深地感到自己的弱小。