fast_photon
2025-01-09 13:43:33
绝世 NL 题。
标算是单
考虑到,如果一个正方形合法,那么完全被它包含的正方形都没有贡献。因此只需要考虑极大正方形。(极大正方形:固定任意一个顶点,将另一个顶点背离它斜向走一格,得到的正方形总是不合法)。由此可以想到,对于每个格子,以它为左上角的极大正方形存在且唯一。
固定左上角后发现大于极大正方形的一定不合法,小于等于的一定合法,可以通过二维前缀和算区域内障碍个数判断区域是否合法。现在我们得到了
一刻也没有为扫描线的难处理感到烦恼,立刻赶到现场的是二维数据结构!直接使用类似二维线段树的东西:
总复杂度
#include<iostream>
#include<cstdio>
using namespace std;
int n, m, a[2005][2005], s[2005][2005];
short f[2005][2005][11][11];
string A[2005];
int lg2(int x) {
int k = 1, cnt = 0;
while(k < x) k <<= 1, cnt++;
return cnt;
}
inline int lowbit(int x) { return x & -x; }
inline int highbit(int x) { return 1 << (31 - __builtin_clz(x)); }
void flush(int i, int j, int di, int dj, int l) {
int k1, k2;
for(int x = i + di - 1; x >= i; x -= (1 << k1)) {
k1 = __builtin_ctz(min(lowbit(x), highbit(x - i + 1)));
for(int y = j + dj - 1; y >= j; y -= (1 << k2)) {
k2 = __builtin_ctz(min(lowbit(y), highbit(y - j + 1)));
f[x][y][k1][k2] = max(f[x][y][k1][k2], (short)l);
}
}
}
signed main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> n >> m;
for(int i = 1; i <= n; i++) {
cin >> A[i];
for(int j = 1; j <= m; j++) {
a[i][j] = (A[i][j - 1] == '#');
s[i][j] = s[i][j - 1] + s[i - 1][j] - s[i - 1][j - 1] + a[i][j];
}
}
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
int l = 0, r = min(n - i + 1, m - j + 1);
while(l < r) {
int mid = (l + r + 1) >> 1;
if(s[i + mid - 1][j + mid - 1] - s[i - 1][j + mid - 1] - s[i + mid - 1][j - 1] + s[i - 1][j - 1]) r = mid - 1;
else l = mid;
}
if(i >= 2 && j >= 2 && j >= 2 && !(s[i + l - 1][j - 1] - s[i - 1][j - 1] - s[i + l - 1][j - 2] + s[i - 1][j - 2] + s[i - 1][j + l - 1] - s[i - 1][j - 1] - s[i - 2][j + l - 1] + s[i - 2][j - 1] + a[i - 1][j - 1])) continue;
flush(i, j, l, l, l);
}
}
for(int u = 10; u > 0; u--) {
for(int v = 10; v >= 0; v--) {
for(int i = (1 << u); i <= n; i += (1 << u)) {
for(int j = (1 << v); j <= m; j += (1 << v)) {
f[i][j][u - 1][v] = max(f[i][j][u - 1][v], f[i][j][u][v]);
f[i - (1 << (u - 1))][j][u - 1][v] = max(f[i - (1 << (u - 1))][j][u - 1][v], f[i][j][u][v]);
}
}
}
}
for(int u = 10; u >= 0; u--) {
for(int v = 10; v > 0; v--) {
for(int i = (1 << u); i <= n; i += (1 << u)) {
for(int j = (1 << v); j <= m; j += (1 << v)) {
f[i][j][u][v - 1] = max(f[i][j][u][v - 1], f[i][j][u][v]);
f[i][j - (1 << (v - 1))][u][v - 1] = max(f[i][j - (1 << (v - 1))][u][v - 1], f[i][j][u][v]);
}
}
}
}
int q; cin >> q; while(q--) {
int x, y; cin >> x >> y;
cout << (int)f[x][y][0][0] * f[x][y][0][0] << '\n';
}
}
首先二分矩形边长是无比丑陋的,考虑如下维护方式:
现在只有在数据结构上更新矩形的部分是
注意到
发现每个矩形一定被右侧、下侧或右下的某个障碍卡住。右侧和下侧是对称的。
这样的代价是容易分析的:同一个方向(正右、右下、正下)不会 ban 掉一个格子两次(否则这两次 ban 的发出者一定会先有一个 ban 掉另一个)。因此,每个格子最多被 ban 掉
这样总复杂度就是
for(int i = 1; i <= n; i++) nxr[i][m + 1] = m + 1, nxd[i][m + 1] = i;
for(int j = 1; j <= m + 1; j++) nxr[n + 1][j] = j, nxd[n + 1][j] = n + 1;
for(int i = n; i >= 1; i--) {
for(int j = m; j >= 1; j--) {
if(a[i][j]) len[i][j] = 0, nxr[i][j] = j, nxd[i][j] = i;
else {
nxr[i][j] = nxr[i][j + 1], nxd[i][j] = nxd[i + 1][j];
if(!sum(i, j, i + len[i + 1][j + 1], j + len[i + 1][j + 1])) {
len[i][j] = len[i + 1][j + 1] + 1;
}
else len[i][j] = min(nxr[i][j] - j, nxd[i][j] - i);
}
}
}
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
int l = len[i][j];
if(!l || mk[i][j]) continue;
flush(i, j, l, l, l);
if(nxr[i + l][j] < j + l) {
int k = nxr[i + l][j] - j;
for(int v = 0; v <= k; v++) mk[i + v][j + v] = 1;
for(int v = 0; v < l - k; v++) mk[i + v][j] = 1;
}
else if(nxd[i][j + l] < i + l) {
int k = nxd[i][j + l] - i;
for(int v = 0; v <= k; v++) mk[i + v][j + v] = 1;
for(int v = 0; v < l - k; v++) mk[i][j + v] = 1;
}
else {
for(int v = 0; v < l; v++) mk[i + v][j + v] = 1;
}
}
}
同学给了个单调队列优化成线性的做法,但是没人写代码就先不放了,有空再放上来。