[HAOI2007] 修筑绿化带

  这题挺高能的,但是HAOI2007出两道单调队列+降维的题,而且几乎一样,这样真的好吗......

  首先大体的思路。

  假设我们已经知道了对每一个 A*B 的矩阵,被其包含的所有 C*D 矩阵的最小和。

  那我们只需要枚举左上角,在 O(1) 计算更新最优解就行了。

  考虑实现上述思路。

  1:二为前缀和预处理,并将每一个 C*D 矩阵的和存在 c[i][j] 中,(i,j) 为该矩阵左上顶点坐标。

  2:对数组 c[i][j] 求平面区间 (A-C-1)*(B-D-1) 的最小值存储在 d[i][j] 中。

  3:枚举求解。

  平面最值的处理方式与理想的正方形一题相同。

  这里需要注意的是平面范围的确定,因为 C*D 矩阵要完全包含在 A*B 矩阵内,注意完全包含,可以得出上述平面间范围。

  我觉得细节是比较多的,写的时候很小心,所以写完后提交一遍过也很激动。

  如果不A的话估计又是无限期的调试......

// q.c

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>

using namespace std;

const int MAX=1000+10;

int M,N,A,B,C,D,ans;
int a[MAX][MAX]; // 原数组.
int c[MAX][MAX]; // C*D矩阵和数组.
int d[MAX][MAX]; // C*D矩阵和在A*B内最小值数组.
int s[MAX][MAX]; // 前缀和数组.
int minx[MAX][MAX]; // C*D矩阵和在x方向区间最小值数组.

int qMin[MAX],h,t,pos[MAX]; // 单调队列相关.
void reset() {
	memset(qMin,0,sizeof(qMin));
	memset(pos,0,sizeof(pos));
	h=1,t=0;
}
void insert(int x,int y,int z,int r) { // w[x][y]=r,z为行或列方向坐标.
	while(h<=t&&qMin[t]>=r) t--;
	qMin[++t]=r,pos[t]=z;
}

int main() {
	
	freopen("parterre.in","r",stdin);
	freopen("parterre.out","w",stdout);
	
	scanf("%d%d%d%d%d%d",&M,&N,&A,&B,&C,&D);
	for(int i=1;i<=M;i++) for(int j=1;j<=N;j++) scanf("%d",&a[i][j]);
	
	for(int i=1;i<=M;i++) for(int j=1;j<=N;j++) // 求二维前缀和.
		s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
	
	for(int i=1;i+C-1<=M;i++) for(int j=1;j+D-1<=N;j++) // 求C*D矩阵和.
		c[i][j]=s[i+C-1][j+D-1]-s[i-1][j+D-1]-s[i+C-1][j-1]+s[i-1][j-1];
	
	int xLen=A-C-1; // x方向最值的区间长度. (C*D矩阵完全包含在A*B矩阵内)
	int yLen=B-D-1; // y方向最值的区间长度. (C*D矩阵完全包含在A*B矩阵内)
	int xSide=M-C+1; // x的新边界. (对于C*D矩阵)
	int ySide=N-D+1; // y的新边界. (对于C*D矩阵)
	
	for(int i=1;i<=xSide;i++) { // 求每一行y方向长度为yLen区间最小值,以降维处理.
		reset();
		for(int j=1;j<=yLen;j++) { // 先入队yLen个元素.
			insert(i,j,j,c[i][j]);
		}
		for(int j=yLen+1;j<=ySide+1;j++) { // 边界区间是[ySide-yLen+1,ySide].
			minx[i][j-yLen]=qMin[h];
			if(j==ySide+1) break;
			insert(i,j,j,c[i][j]);
			if(pos[h]<=j-yLen) h++; // 不在区间范围内了.
		}
	}
	
	for(int j=1;j<=ySide;j++) { // 求每一列x方向长度为xLen区间最小值,此时minx已存储一维最值.
		reset();
		for(int i=1;i<=xLen;i++) {
			insert(i,j,i,minx[i][j]);
		}
		for(int i=xLen+1;i<=xSide+1;i++) { // 边界区间是[xSide-xLen+1,xSide].
			d[i-xLen][j]=qMin[h]; // 现在d已存储二维区间最值.
			if(i==xSide+1) break;
			insert(i,j,i,minx[i][j]);
			if(pos[h]<=i-xLen) h++;
		}
	}
	
	for(int i=1;i+A-1<=M;i++) { // 枚举左上顶点,O(1)得到该点最优值.
		for(int j=1;j+B-1<=N;j++) {
			int x=s[i+A-1][j+B-1]-s[i-1][j+B-1]-s[i+A-1][j-1]+s[i-1][j-1];
			ans=max(ans,x-d[i+1][j+1]);
		}
	}
	
	printf("%d\n",ans);
	
	return 0;
}

 

posted @ 2018-04-11 15:43  qjs12  阅读(108)  评论(0编辑  收藏  举报