[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; }