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
",ans);
	return 0;
}

深深地感到自己的弱小。

原文地址:https://www.cnblogs.com/syzf2222/p/14027767.html