题意:有个$n\times n,n\leq 5000$的矩阵,挖掉$m,m\leq 10^6$个格子,问不包括这些格子的子矩阵个数。
做法很容易考虑:枚举一行,这一行从左往右扫,依次计算每个格子为右下角的子矩阵个数。
扫的时候维护一个单调栈,里面存(元素,以这个元素为高度的格子最长向右延伸了多少格)
为什么要存第二个项呢?我们维护的单调栈里面,每个东西实际上表示的是一个矩形,含义是:在我们处理完当前这个列后,这些矩形里的元素都可以作为合法的子矩阵左上顶点。
扫的时候维护一个东西:单调栈里所有矩形的大小和$sum$,即合法的左上顶点个数。
每次加入一个元素$x$,分以下三种情况讨论:
- $x$大于单调栈最后的元素:直接加入。长度记为1,sum加上$x$(显然多了这么多合法的格子)
- $x$等于单调栈最后的元素:单调栈最后的元素的长度加1,sum加上$x$
- $x$小于单调栈最后的元素:弹出单调栈最后的元素,并把其长度加到倒数第二个元素上,然后重复这三个check
最后我们每插入一个元素后,所维护的sum就是以这个点为右下角的答案。
另外有一个坑:输入1625 0,答案输出正确,但是从1626 0开始,答案越来越小,看起来像是溢出了,但是开-fsanitize=undefined没报任何问题,结果最后查出来了,算一行的答案时使用的是std::accumlate,初始值传的是int类型,而后累加变量自动推导为int,导致这个函数里面溢出了,然后由于ubsan并不会对包含的其他文件的代码做检查,于是挂掉了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
#include <assert.h> #include <algorithm> #include <iostream> #include <numeric> #include <utility> #include <vector> using int_t = long long; using std::cin; using std::cout; using std::endl; /** * 1624 0 -> R * 1625 0 -> R * 1626 0 -> E 正确1749670208001 本程序 1736785306113 * 1627 0 -> E */ bool block[5001][5001]; int_t upmost[5001][5001]; int_t n, m; int main() { std::ios::sync_with_stdio(false); cin >> n >> m; for (int_t i = 1; i <= m; i++) { int_t r, c; cin >> r >> c; block[r][c] = true; } for (int_t i = 1; i <= n; i++) { //列 int_t lastpos = 0; for (int_t j = 1; j <= n; j++) { #ifdef DEBUG // cout << "block " << j << " " << i << " = " << block[j][i] << // endl; #endif if (block[j][i]) lastpos = j; upmost[j][i] = lastpos; #ifdef DEBUG cout << "upmost " << j << " " << i << " = " << upmost[j][i] << endl; #endif } } int_t result = 0; //枚举底边行号 for (int_t i = 1; i <= n; i++) { static int_t answer[5001]; // j->以(i,j)为右下角的矩形个数 //以当前元素为右下角的矩形个数 int_t sum = 0; // first为元素,second为出现次数 std::vector<std::pair<int_t, int_t>> stack; stack.emplace_back(0, 0); for (int_t j = 1; j <= n; j++) { int_t x = i - upmost[i][j]; //向上延伸的空格子数(包括i,j) int_t count = 1; while (x < stack.back().first) { count += stack.back().second; sum -= stack.back().first * stack.back().second; stack.pop_back(); } assert(sum >= 0); if (x == stack.back().first) { stack.back().second += count; } else { stack.emplace_back(x, count); } sum += x * count; answer[j] = sum; // cout << "answer col " << j << " = " << answer[j] << endl; #ifdef DEBUG cout << "pushed col " << j << " val " << x << " sum = " << sum << " answer = " << answer[j] << endl; #endif } int_t currrow = std::accumulate(answer + 1, answer + 1 + n, (int_t)0); result += currrow; // cout << "answer row " << i << " = " << currrow << " " << currrow / i // << " " << currrow % i << endl; #ifdef DEBUG cout << "answer row " << i << " = " << currrow << endl; #endif } cout << result << endl; return 0; } |