Codeforces 1195E OpenStreetMap 单调队列套单调队列
题意:给你一个n * m的矩阵,问所有的a * b的子矩阵的最小的元素的和是多少。题目给出了矩阵中的数的数据生成器。
思路:如果这个问题是1维的,即求所有区间的最小元素的和,用单调队列O(n)就可以做。对于这个问题,我们先给每一行建一个单调队列,枚举子矩阵的行坐标的左端点和右端点。在行的左右端点的基础上,用另一个单调队列维护子矩阵每一行的最小值。
代码:
#include <bits/stdc++.h> #define INF 0x3f3f3f3f #define pii pair<int, int> #define LL long long #define db double using namespace std; const int maxn = 3001; pii q[maxn]; int l, r; int q3[maxn][maxn], l3[maxn], r3[maxn]; int a[maxn][maxn]; int n, m; void init(int p, int L, int R) { for (int j = L; j <= R; j++) { int tmp = a[p][j]; while(l3[p] <= r3[p] && a[p][q3[p][r3[p]]] >= tmp) r3[p]--; q3[p][++r3[p]] = j; } } void maintain(int p, int L, int R) { int tmp = a[p][R]; while(l3[p] <= r3[p] && q3[p][l3[p]] < L) l3[p]++; while(l3[p] <= r3[p] && a[p][q3[p][r3[p]]] >= tmp) r3[p]--; q3[p][++r3[p]] = R; } int query(int p) { return a[p][q3[p][l3[p]]]; } int main() { int a1, b1, now, x, y, z; scanf("%d%d%d%d", &n, &m, &a1, &b1); scanf("%d%d%d%d", &now, &x, &y, &z); for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { a[i][j] = now; now = ((LL)now * x + y) % z; } } for (int i = 1; i <= n; i++) l3[i] = 1; LL ans = 0; for (int i = 1; i <= n; i++) { init(i, 1, b1 - 1); } for (int l1 = 1, r1 = b1; r1 <= m; l1++, r1++) { l = 1, r = 0; for (int j = 1; j <= a1; j++) { maintain(j, l1, r1); int tmp = query(j); while(l <= r && q[r].second >= tmp) r--; q[++r] = make_pair(j, tmp); } ans += q[l].second; for (int l2 = 2, r2 = a1 + 1; r2 <= n; l2++, r2++) { while(l <= r && q[l].first < l2) l++; maintain(r2, l1, r1); int tmp = query(r2); while(l <= r && q[r].second >= tmp) r--; q[++r] = make_pair(r2, tmp); ans += q[l].second; } } printf("%lld\n", ans); }