2019HDU多校训练第三场 Planting Trees 暴力 + 单调队列优化
题意:有一个n * n的网格,每个网格中间有一颗树,你知道每棵树的高,你可以选择一个矩形区域把里面的树都围起来,但是矩形区域里面任意两棵树的高度差的绝对值不超过m,问这个矩形的最大面积是多少?
思路:前两天的牛客多校有一个最大子矩形问题,当时用的扫描线 + 单调栈过的,结果场上想了半天灭结果QAQ。这个题有限制条件就不好那么做。注意到题目中的信息,可以用O(n ^ 3)的算法做,如果我们枚举矩阵的左上角和右下角是O(n ^ 4),而且没什么优化手段,不行。但是我们转化一下思路,我们枚举矩形的上边界和下边界以及右边界,我们想一下有没有办法在均摊O(1)的时间内找到对应右边界最优的左边界。怎么找左边界呢?首先我们得判断当前左边界和其它三个边界围成的矩形中最大值和最小值的差有没有超过m,如果没有说明是合法的。维护区间的最大最小值,有各种数据结构,但是单调队列是性价比最高的,因为每次右边界只增加1,和单调队列契合度很高,还可以做到均摊O(1)。那么,我们维护一个最小值和一个最大值的单调队列以及左边界。右边界移动一次后,将上下边界之间的右边界所在列的最小值和最大值分辨压入两个单调队列,如果当前矩形内的差值大于m,就移动左边界。如果单调队列队首位置小于左边界了就pop,更新答案。
代码:
#include <bits/stdc++.h> using namespace std; const int maxn = 510; int q1[maxn], q2[maxn], l1, l2, r1, r2; int mi[maxn], mx[maxn], a[maxn][maxn]; int main() { int T, n, m; scanf("%d", &T); while(T--) { int ans = 0; scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) { scanf("%d", &a[i][j]); } for (int i = 1; i <= n; i++) { memset(mi, 0x3f, sizeof(mi)); memset(mx, 0, sizeof(mx)); for (int j = i; j <= n; j++) { l1 = l2 = 1, r1 = r2 = 0; int pos = 1; for (int k = 1; k <= n; k++) { mi[k] = min(mi[k], a[j][k]); mx[k] = max(mx[k], a[j][k]); while(l1 <= r1 && mi[q1[r1]] > mi[k]) r1--; q1[++r1] = k; while(l2 <= r2 && mx[q2[r2]] < mx[k]) r2--; q2[++r2] = k; while(pos <= k && mx[q2[l2]] - mi[q1[l1]] > m) { pos++; if(q1[l1] < pos) l1++; if(q2[l2] < pos) l2++; } if(l1 <= r1 && l2 <= r2) { ans = max(ans, (j - i + 1) * (k - pos + 1)); } } } } printf("%d\n", ans); } }