pair<int, int> find_p(int x)
{
for (int i = 0; i < m; i++) {
pair<int, int> s(x + d1 * i, y + d2 * i);
// 如果发现 s 不在黑点集合中,返回 s。
if (!binary_search(o + 1, o + 1 + k, s)) {
return s;
}
}
return {x, m + 1};
}
统计可能成为答案的点:
int bf = m + 1; // 之前行最左边的点的横坐标。
for (int i = 1, bf = m + 1; i <= n; i++) {
auto x = find_p(i);
if (x.second < bf) {
// 将 x 加入可能成为关键点集中。
qr.push_back(x);
bf = x.second;
}
// 若 bf <= 1 那么显然不会有更左的点了,
// 直接退出循环,否则复杂度显然是错误的。
if (bf <= 1) break;
}
对于其它方向(总共八个方向)都可以类似地求。
这个做法复杂度比较高,不过出题人没有卡。我造了组极限数据,这种做法要跑 900 ms,也确实能过。
解法二
不同于解法一,我们考虑如何优化计算,给定一个白点 $(a, b)$,其到所有曼哈顿距离之和。
这有个板子题,是 Gym100739E Life as a monster。
首先横纵坐标的贡献可以拆开算。就拿横坐标来说,将所有黑点分类成 $L = \{x\mid x \leq a\}$ 和 $R = \{x\mid x > a\}$ 两种。那么答案是:
$$
\left(|L|\cdot a - \sum_{i\in L} i\right) + \left(-|R|\cdot a + \sum_{i\in R}i \right)
$$
可以离散化后,前缀和计算这个答案。带修可以树状数组,强制在线就平衡树。本题前缀和即可。
枚举所有白点计算答案肯定是不行的。还是可以用一下**主要思路**中提到的结论,不过我们不需要那么麻烦。注意到所有关键点(除了四个角)一定和某个黑点相邻。所以枚举黑点相邻的点即可。最后如果四个角有白色的,加上这些的贡献即可。复杂度 $O(k\log k)$,瓶颈为离散化。
代码也很好写,我就不放了。
其实在此基础上,我们发现并不需要离散化这个步骤,只需要排序(扫描线)。比如就拿横坐标贡献来说。将所有点按照横坐标升序排序,从左到右遍历每个黑点,计算当前前缀和即可。总复杂度 $O(k\log k)$。
### 解法三
$O(k)$。
在解法一的基础上,进一步地,可以发现每个关键点离边界较大的距离不超过 $k$。这个可以从求解关键点的过程的复杂度得到。
所以,在解法二的基础上,我们只需要对横坐标小于等于 $k$ 的点计数排序(或开个桶前缀和)即可。只用遍历离边界不超过 $k$ 的黑点,剩下的点一定在关键点的另一侧,可以 $O(1)$ 计算贡献。总复杂度 $O(k)$。