NKOJ8856 矩形

给出一个 \(n\times n\) 的网格,有些格子是黑色的剩下是白色的。有 \(m\) 次询问:询问网格中有多少 \(a\) 行 \(b\) 列的矩形,满足它的四条边上所有格子都是黑的。

注意:空间限制为128M。

\(1\le n,m\le 1500\)

最暴力的做法:每次询问都枚举矩形的左上角,前缀和判一下是否四条边都是黑的,\(O(mn^2)\) 直接去世。
那么我们考虑对于一组给定的 \(a,b\),我们知道哪些信息就能快速求出答案。

首先,满足要求的矩形上面的边和下面的边是全黑的。因此我们枚举上面的边和下面的边所处的行,对每一行维护一个 bitset,第 \(i\) 行的 bitset 第 \(j\) 位为 \(1\) 表示 \((i,j),(i,j+1)...(i,j+a-1)\) 全为黑色。记第 \(i\) 行的 bitset 为 \(s_i\)。
那么满足上面的边和下面的边是全黑的,且上面的边在第 \(i\) 行,下面的边在第 \(j\) 行的矩形数量为 \(\vert s_i\and s_j\vert\)。
这一步需要枚举上下的边所在行,然后还要 bitset 操作一下,时间复杂度 \(O(\frac{n^3}{\omega})\)。

但这个东西对于固定的 \(a\) 才有用,当 \(a\) 发生变化时,\(s\) 也会变。
所以将询问离线下来按 \(a\) 排序,为了方便接下来处理 \(s\) 的变化,我们再对于每一行维护一个 bitset 叫做 \(d\),\(d_{i,j}=1\) 表示 \((i,j)\) 为黑色,\(d_{i,j}=0\) 表示 \((i,j)\) 为白色。
每当 \(a\) 加 \(1\) 时,\(s_i\&=d_i>>a-1\)。这里 \(a\) 指变化后的 \(a\)。右移是因为 bitset 是从高位到低位存的。
每次修改耗时 \(O(\frac{n^2}{\omega})\),总共进行 \(n\) 次修改,总耗时 \(O(\frac{n^3}{\omega})\)。

以上算的都是上面的边和下面的边,我们还要考虑左右的边。

先考虑左边的边。由于上面我们枚举了矩形上下的边,记上面的边为 \(l\),下面的边为 \(r\),因此如果一个左上角 \((l,i)\) 满足条件,则 \(a_{l,i}=1,a_{l+1,i}=1...a_{r,i}=1\)。

到这里就有一个想法呼之欲出:把 \(l\) 行到 \(r\) 行的 \(d\) 求交集,得到的是左边全是黑色的矩形左上角列号集合;再把它右移 \(b-1\) 位,得到右边全是黑色的矩形左上角列号集合;再把这两个集合与 \(s_l,s_r\) 求交集,求得的集合就是满足要求的矩形左上角列号集合。

但是这样做复杂度是 \(O(\frac{n^4}{\omega})\) 的,原地起飞。

我们考虑优化求 \(l\) 行到 \(r\) 行的 \(d\) 的交集这一步。区间集合快速求交,直接用 st 表就行了!好恶心啊其实写起来非常非常短。

那这样复杂度 \(O(\frac{n^3+mn^2}{\omega})\),费了好大力气就比暴力快了一个 \(\omega\) 但它就是过了

nkoj 这题时限开了 5s,我跑了 2s 不到,放其它 OJ 开 1s 其实绰绰有余。

顺便提一句,这题官方题解做法又臭又长又慢,这个做法是机房巨佬想出来的

#include <cstdio>
#include <bitset>
#include <algorithm>

struct Quest {
	int a, b, id;
	inline bool operator < (const Quest x) const {return b < x.b;}
} q[1505];

std::bitset<1505> s1[1505], s2[1505], st[1505][12], tmp;
char str[1505];
int Log[1505], ans[1505];

int main() {
	int n, m;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; ++ i) {
		scanf("%s", str);
		for (int j = 0; j < n; ++ j) s1[i][j] = str[j] - '0';
		st[i][0] = s1[i], s2[i].set();
	}
	for (int i = 2; i <= n; ++ i) Log[i] = Log[i >> 1] + 1;
	for (int j = 1; 1 << j <= n; ++ j)
	for (int i = 1; i + (1 << j) - 1 <= n; ++ i)
		st[i][j] = st[i][j - 1] & st[i + (1 << j - 1)][j - 1];
	for (int i = 1; i <= m; ++ i) scanf("%d%d", &q[i].a, &q[i].b), q[i].id = i;
	std::sort(q + 1, q + m + 1);
	for (int i = 1; i <= m; ++ i) {
		for (int j = q[i - 1].b + 1; j <= q[i].b; ++ j)
			for (int k = 1; k <= n; ++ k) s2[k] &= (s1[k] >> j - 1);
		int d = Log[q[i].a];
		for (int j = 1; j + q[i].a - 1 <= n; ++ j) {
			int k = j + q[i].a - 1;
			tmp = st[j][d] & st[k - (1 << d) + 1][d];
			ans[q[i].id] += (s2[j] & s2[k] & tmp & tmp >> q[i].b - 1).count();
		}
	}
	for (int i = 1; i <= m; ++ i) printf("%d\n", ans[i]);
	return 0;
}
上一篇:牛客挑战赛53 B.简单的序列(bitset,哥德巴赫猜想)


下一篇:日常Java 2021/10/14