Description
有一个n*m*h的切糕,你需要给所有n*m条纵轴确定一个切点(x,y,z),表示切掉坐标为(x,y,z)的点。规定相邻的纵轴不能相距太远,具体若两条纵轴(x1,y1),(x2,y2) 四联通,那么要求满足 |z1-z2|<=D。其中D是给定的常数。每个点有不和谐度,请求出切出的点的最小不和谐度。 n,m,h<=40。
Solution
第一眼有最大权闭合子图的感觉,但是又不是很一样。最大权闭合子图是规定了如果选择了一个点那么它连出去的所有点都要选择,但是这里只用选择连出去的所有出边中的一个点即可。
这种规定某些点只能取其一的模型,放在最大流里就是新建一个节点,源点向这个节点连容量为1的边,然后再从这个节点向其它节点连容量为1的边,但是这样就处理不了点权的问题了,所以我们考虑最小割。那这种模型放在最小割里就是把这些节点连成一串,把点权反映到边权上,如果割掉了某条边就代表选/不选这个点,因为求出的是最小割,那这一串边只会选择某一条割掉,也就是说只会选择一个点。
再加上D的限制就做完了。其实有D也很简单,就是规定了如果割掉某条边之后一定要在这条边对应的点的连出去的出边中选择一条割掉。假设割掉了(x,y,z),那对于(x-1,y)这条纵轴,我们割掉的z'一定满足z-D<=z'<=z+D,那么可以从(x,y,z)向(x-1,y,z-D)连一条容量为inf的边,从(x-1,y,z+D+1)向(x,y,z+1)连一条容量为inf的边,表示如果不割掉z-D<=z'<=z+D中的点的话那这就不是一个合法的割了,这样就保证了答案的正确性。
Code
#include<bits/stdc++.h>
using std::min;
using std::max;
using std::swap;
using std::vector;
typedef double db;
typedef long long ll;
#define pb(A) push_back(A)
#define pii std::pair<int,int>
#define all(A) A.begin(),A.end()
#define mp(A,B) std::make_pair(A,B)
const int N=1e5+5;
const int inf=1e9;int n,m,h,D,s,t,cnt=1;
int head[N],d[N],val[41][41][41];
int dx[]={-1,0,1,0},dy[]={0,1,0,-1};struct Edge{int to,nxt,flow;
}edge[5120000];void add(int x,int y,int z){edge[++cnt].to=y;edge[cnt].nxt=head[x];edge[cnt].flow=z;head[x]=cnt;edge[++cnt].to=x;edge[cnt].nxt=head[y];edge[cnt].flow=0;head[y]=cnt;
}bool bfs(){std::queue<int> q;q.push(s);memset(d,0,sizeof d);d[s]=1;while(q.size()){int u=q.front();q.pop();for(int i=head[u];i;i=edge[i].nxt){int to=edge[i].to;if(!d[to] and edge[i].flow){d[to]=d[u]+1;q.push(to);if(to==t) return 1;}}} return 0;
}int dinic(int now,int flow){if(now==t) return flow;int res=flow;for(int i=head[now];i;i=edge[i].nxt){int to=edge[i].to;if(d[to]==d[now]+1 and edge[i].flow){int k=dinic(to,min(res,edge[i].flow));if(!k) d[to]=0;edge[i].flow-=k,edge[i^1].flow+=k,res-=k;if(!res) return flow;}} return flow-res;
}int getint(){int X=0,w=0;char ch=getchar();while(!isdigit(ch))w|=ch=='-',ch=getchar();while( isdigit(ch))X=X*10+ch-48,ch=getchar();if(w) return -X;return X;
}int num(int x,int y,int z){return n*m*(z-1)+(x-1)*m+y;
}int idx(int x,int y){return n*m*h+(x-1)*m+y;
}signed main(){n=getint(),m=getint(),h=getint(),D=getint();t=n*m*h+n*m+5;for(int z=1;z<=h;z++)for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)val[i][j][z]=getint();for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){add(s,num(i,j,1),inf);for(int z=1;z<h;z++)add(num(i,j,z),num(i,j,z+1),val[i][j][z]);add(num(i,j,h),idx(i,j),val[i][j][h]);add(idx(i,j),t,inf);}for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){for(int k=0;k<4;k++){int nx=i+dx[k],ny=j+dy[k];if(nx>=1 and nx<=n and ny>=1 and ny<=m){for(int z=1;z<=h;z++){add(num(i,j,z),num(nx,ny,min(z-D,1)),inf);if(z+D+1<=h) {if(z!=h) add(num(nx,ny,z+D+1),num(i,j,z+1),inf);else add(num(nx,ny,z+D+1),idx(i,j),inf);}else{if(z+1<=h)add(idx(nx,ny),num(i,j,z+1),inf);else add(idx(nx,ny),idx(i,j),inf);}}}}}} int mxflow=0,flow=0;while(bfs()) while(flow=dinic(s,inf)) mxflow+=flow;printf("%d\n",mxflow);return 0;
}