P2216 [HAOI2007]理想的正方形 - 单调队列

这种矩形问题常用单调队列优化枚举(通过贪心取最优降低了一个维度的枚举)
推荐这道题也要做一做:[ZJOI2007]棋盘制作
单调队列的空间记得开大点! 反正内存够用
注意,这题正方形边长是固定的!
暴力算法是枚举上边界所在的行,左边界所在的列,通过这两个个信息确定一个正方形,然后预处理出一行中从第i个点到i+n个点的最值,再扫一遍这个正方形的行,复杂度是N^3

预处理的时候,复杂度也是N^3。。。考虑用单调队列来维护,枚举到一行,看做一个序列,求长度为n的区间最值,就是一个滑动窗口,如果不大明白的话去做一下这道题:P1886 滑动窗口

求答案的时候,也可以用单调队列优化掉一层枚举,那么优化掉行的枚举吧。行不再需要枚举,只需要在每列枚举的时候维护单调队列,因为正方形边长是固定的,只需要知道所在的列是什么,就可以知道所在的行

以求最大值为例:枚举到一列的时候,直接扫一遍行,我们不是预处理出一个f[i][j]表示包括第i行第j列的那个元素往后n个元素的最值了么,把他放到一个单调递减的队列里面,并且维护队列长度不超过n,此时这个正方形的最大值就是队头。

仔细想想,若目前有一个元素比队列里面的元素都要大,那么在以后求最大值的时候我便不需要知道这个元素之前的那些元素具体是什么,因为目前有个更大且能生存更长时间的元素,所以前面的那些都可以扔掉,直接出队就好了,维护时效性与最优性

!!!注意,更新答案之前必须保证队列里面有且仅有n个元素,也就是说要加一句 i >= n 的特判,不然就会导致还没构成一个正方形就把答案更新了的错误情况

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <cmath>
using namespace std;
#define debug(x) cerr << #x << "=" << x << endl;
const int MAXN = 1000 + 10;
const int INF = 1 << 30;
typedef long long ll;
int n,m,a,b,ans=INF,max_l,max_r,min_l,min_r;
int f[MAXN][MAXN],g[MAXN][MAXN],rec[MAXN][MAXN];
struct st{
    int val, pos;
}qmax[2*MAXN],qmin[2*MAXN];

void max_modify(int k, int pos) {
    while(max_l <= max_r && qmax[max_r].val <= k) max_r--;
    qmax[++max_r].val = k, qmax[max_r].pos = pos;
    while(max_l <= max_r && abs(qmax[max_r].pos-qmax[max_l].pos) + 1 > n) max_l++;
}

void min_modify(int k, int pos) {
    while(min_l <= min_r && qmin[min_r].val >= k) min_r--;
    qmin[++min_r].val = k, qmin[min_r].pos = pos;
    while(min_l <= min_r && abs(qmin[min_r].pos-qmin[min_l].pos) + 1 > n) min_l++;
}

void init() {
    
    for(int i=1; i<=a; i++) {
        max_l = 1, max_r = 0; // 重置队列,不然的话空间就得开N^2大小,要不然就会RE,仔细想想为什么
        min_l = 1, min_r = 0;
        for(int j=b; j; j--) {
            max_modify(rec[i][j], j);
            min_modify(rec[i][j], j); // 每个点进队一次 出队一次
            f[i][j] = qmax[max_l].val;
            g[i][j] = qmin[min_l].val;
        }
    }
}

void work() {
    for(int j=n; j<=b; j++) {
        int pos = j - n + 1;
        max_l = 1, max_r = 0;
        min_l = 1, min_r = 0;
        for(int i=1; i<=a; i++) {
            max_modify(f[i][pos], i);
            min_modify(g[i][pos], i);
            if(i >= n) ans = min(ans, qmax[max_l].val - qmin[min_l].val);
        }
    }
}

int main() {       
    scanf("%d%d%d", &a, &b, &n);
    for(int i=1; i<=a; i++) {
        for(int j=1; j<=b; j++) {
            scanf("%d", &rec[i][j]);
        }
    }
    init();
    work();
    printf("%d\n", ans);
    return 0;
}
posted @ 2018-10-28 09:48  Zolrk  阅读(126)  评论(0编辑  收藏  举报