专栏文章

AT_abc410_f [ABC410F] Balanced Rectangles 题解

AT_abc410_f题解参与者 2已保存评论 1

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
1 条
当前快照
1 份
快照标识符
@mip35mo4
此快照首次捕获于
2025/12/03 05:24
3 个月前
此快照最后确认于
2025/12/03 05:24
3 个月前
查看原文

前言

最近几场可以切 A~E 了,所以赛时没切掉 F。而且最最令人开心的是只 WA 了一个点的快感,可惜 Atcoder 不给部分分。

小细节

代码中可能出现的错误就放这里了,作者是用的数组和 vector,所以对使用 map 的家人们可能帮助不大。下文无特殊说明默认通过了样例(如果没过且思路正确可以留言让我帮你调):
  • 样例 RE 了。
    • 可能是数组下标访问到负数了,需要在进行统计时初始值赋值为 H×WH \times W,因为如果整个矩形都是您定义为 1-1 的那个字符,就会炸掉。
    • 可能是数组开小了,请分析您数组访问时是否越界。
    • 还有可能是其他某些地方您写错了,导致 RE,包括其他地方的数组访问。
  • WA × 4\times\ 4,可能是您存储需要还原的下标没有存 H×WH \times W,您可能觉得之后反正都要将其赋值为 11,没有必要,但是别忘了这题有多测,下一次访问时不一定还是这个位置。
  • WA × 2\times\ 2,可能是您存储需要还原的下标有问题,比如一开始加进容器的是 HH 而不是 H×WH \times W
  • WA × 1\times\ 1,可能是您跟我一样在最后将统计数组清空时是用 int k = v.size() - 1,然后进行操作再 pop_back(),再将变量的值减一,并且判的是 while (k),那么改成 while (k + 1) 即可。
如果都没有问题,那么只有您自己调试了。

解题思路

由于题目要求选出一个矩形,使得里面包含 . 的数量等于 # 的数量,所以我们可以将 . 视为 11# 视为 1-1,问题就转化为了求内部所有元素之和为 00 的矩形数量。
求解的话需要枚举矩形左上角和右下角再进行求和,于是一个 O(H3W3)\mathcal{O}(H^3W^3) 的超朴素算法就出来了。再使用前缀和优化一下可以做到 O(H2W3)\mathcal{O}(H^2W^3)
但是注意到数据范围 HW3×105HW \leq 3 \times 10^5,显然 T 到下辈子,因此可以分讨一下。
  • HWH \leq W,那我们可以预处理每一行的前缀和,枚举的时候只需要枚举 HH,相较于枚举 WW 减少了一点时间复杂度,为 O(H3W2)\mathcal{O}(H^3W^2)
  • H>WH > W,那我们可以预处理每一列的前缀和,同理,时间复杂度为 O(H2W3)\mathcal{O}(H^2W^3)
看这样子应该不可以优化了,但是给我们提供了一种思路,如果可以做到 O(HW×min(H,W))O(HW \times \min(H,W)) 的话是可以通过的。先考虑 H=1,W=3×105H = 1, W = 3\times 10^5 的情况,复杂度为 O(H2W)\mathcal{O}(H^2W),显然不会超时,只有当 H=3×105,W=3×105H = \sqrt{3\times 10^5}, W = \sqrt{3\times 10^5} 才会变成 O(nn)\mathcal{O}(n \sqrt{n}) 的时间复杂度(n=3×105n = 3\times 10^5),但是也不会超时,因此我们有了一个大概的方向。
这里才是这道题目最不容易想到的地方吧。注意我说的是“吧”。我们还是跟上面一样进行分讨。
  • HWH \leq W,那么我们就枚举 u,du, d 表示矩形的第一行位于网格的第 uu 行,最后一行位于网格的第 dd 行。然后前缀和出每一列中 udu\sim d 行的和。
    接着就到了重点,首先我们知道对于前缀和数组 sumsum,想要算第 ll 项到第 rr 项的和,只需要计算 sumrsuml1sum_r - sum_{l - 1} 即可,那么我们只需要保证 sumrsuml1=0sum_r - sum_{l - 1} = 0 就能满足题意(即第 uu 行到第 dd 行中第 ll 列到第 rr 列的和为 00),移项可得 sumr=suml1sum_r = sum_{l - 1},由于我们不关心 l1l - 1 的值是多少,因此只需要前面存在 sumk=sumrsum_k = sum_r 即可,因此我们只需要再枚举一个 jj,表示第 jj 列,如果 resres 出现过,就将答案增加出现次数个 11,再 resres+sumjres \to res + sum_j,注意第一次出现 res=0res = 0 时你的答案没有增加,所以需要特殊处理。
  • H>WH > W,同理,是需要将上一种情况的行变成列,列变成行即可。
最坏时间复杂度约为 O(HWHW)\mathcal{O}(HW\sqrt{HW})。别忘了多测要清空还有 resres 可能为负数导致数组越界哦。
都看这么久了,别忘了三连哦!
CODE:
CPP
#include <bits/stdc++.h>
using namespace std;
#define int long long
string s[300010];
int sum[300010], cnt[600010];
vector<int> v;
signed main() {
	ios::sync_with_stdio(false);
	ios_base::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int T;
	cin >> T;
	while (T--) {
		int n, m;
		cin >> n >> m;
		for (int i = 1; i <= n; i++) {
			cin >> s[i];
			s[i] = " " + s[i];
		}
		int ans = 0;
		if (n <= m) {
			for (int u = 1; u <= n; u++) {
				for (int j = 1; j <= m; j++) {
					sum[j] = 0;
				}
				for (int d = u; d <= n; d++) {
					for (int j = 1; j <= m; j++) {
						sum[j] += (s[d][j] == '.' ? 1 : -1);
					}
					cnt[n * m] = 1;
					v.push_back(n * m);
					int res = n * m;
					for (int j = 1; j <= m; j++) {
						res += sum[j];
						if (cnt[res]) {
							ans += cnt[res];
						}
						cnt[res]++;
						v.push_back(res);
					}
					for (int u : v) {
						cnt[u] = 0;
					}
					v.clear();
				}
			}
		} else {
			for (int l = 1; l <= m; l++) {
				for (int i = 1; i <= n; i++) {
					sum[i] = 0;
				}
				for (int r = l; r <= m; r++) {
					for (int i = 1; i <= n; i++) {
						sum[i] += (s[i][r] == '.' ? 1 : -1);
					}
					cnt[n * m] = 1;
					v.push_back(n * m);
					int res = n * m;
					for (int i = 1; i <= n; i++) {
						res += sum[i];
						if (cnt[res]) {
							ans += cnt[res];
						}
						cnt[res]++;
						v.push_back(res);
					}
					for (int u : v) {
						cnt[u] = 0;
					}
					v.clear();
				}
			}
		}
		cout << ans << "\n";
	}
	return 0;
}

评论

1 条评论,欢迎与作者交流。

正在加载评论...