[BZOJ3144]切糕

题目描述

  • 哎,这又是网络流?自闭了。
  • 先考虑只有一个限制条件的情况,求最小收益,转化到求最小割上面。
    在这里插入图片描述
  • 这张图相当于我们把题中的n*m条链抽出来两条,考虑如何通过巧妙地建图,实现对条件的限制。
  • 假如我们有这样一个限制:如果选择了2号点,那么只能选择8~10号点。选择一个点,在图中对应割掉一条边。最小收益我们已经转化为求最小割,考虑增加什么样的边,可以使新图跑最小割的过程中同时满足限制。如果,从2到8号点连一条容量为inf的边,那么假如割掉了2—>3这条边,那么一定会从蓝色的边中割掉一条。这样,通过一条边,我们达到了对后缀的限制。如果是一个可行区间,再从下面的链向上连一条边不就好了?
  • 因此,我们己经会了两条链的情况。本题同样如此,只不过是n*m条链。每条链都这样处理,一条链上相邻点连边的容量为点权,s到第0层的点连容量inf的边,最后一层向t连容量为inf的边。跑最小割即可。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define ll long long
#define rint register int 
using namespace std;
const int N=45*45*45+10;
const int M=3e5+10;
const int inf=1e9;
const int dx[4]={0,0,1,-1};
const int dy[4]={1,-1,0,0};
int P,Q,R,D,s,t,tot=1,ver[M<<1],Next[M<<1],lin[N],edge[M<<1],d[N],v[41][41][41],flow,maxflow;
int cal(int i,int j,int k){return i*P*Q+j*Q+k;}
void add(int x,int y,int z){
	ver[++tot]=y;Next[tot]=lin[x];lin[x]=tot;edge[tot]=z;
	ver[++tot]=x;Next[tot]=lin[y];lin[y]=tot;edge[tot]=0;
}
void init(){
	scanf("%d%d%d%d",&P,&Q,&R,&D);s=N-2,t=N-1;
	for(int i=1;i<=R;++i) for(int j=1;j<=P;++j) for(int k=1;k<=Q;++k) scanf("%d",&v[i][j][k]);
	for(int i=1;i<=P;++i) for(int j=1;j<=Q;++j) add(s,cal(0,i,j),inf),add(cal(R,i,j),t,inf);
	for(int i=1;i<=R;++i) for(int j=1;j<=P;++j) for(int k=1;k<=Q;++k) add(cal(i-1,j,k),cal(i,j,k),v[i][j][k]);
	for(int i=D;i<=R;++i) for(int j=1;j<=P;++j) for(int k=1;k<=Q;++k){
		for(int t=0;t<4;++t){
			int tx=j+dx[t],ty=k+dy[t];
			if(tx<1||tx>P||ty<1||ty>Q) continue;
			add(cal(i,j,k),cal(i-D,tx,ty),inf);
		}
	} 
}
bool bfs(){
	memset(d,0,sizeof(d));
	queue<int>q;
	d[s]=1,q.push(s);
	while(q.size()){
		int x=q.front();q.pop();
		for(int i=lin[x];i;i=Next[i]){
			int y=ver[i];
			if(!d[y]&&edge[i]){
				d[y]=d[x]+1;q.push(y);
				if(y==t) return 1;
			}
		}
	}
	return 0;
}
int dinic(int x,int flow){
	if(x==t) return flow;
	int rest=flow;
	for(int i=lin[x];i&&rest;i=Next[i]){
		int y=ver[i];
		if(edge[i]&&d[y]==d[x]+1){
			int k=dinic(y,min(edge[i],rest));
			if(!k) d[y]=0;
			rest-=k;edge[i]-=k;edge[i^1]+=k;
			if(!rest) return flow-rest;
		}
	}
	return flow-rest;
}
void work(){
	while(bfs()){
		while(flow=dinic(s,inf)) maxflow+=flow;
	}
	cout<<maxflow<<endl;
}
int main(){
	init();
	work();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值