A - 2x2 Erasing
题目位置:
点这里: https://atcoder.jp/contests/arc205/tasks/arc205_a
本专栏提供了整个比赛的题解: link
题目意思翻译
题目描述
首先我们考虑一个 NNN 行 NNN 列的矩阵,我们将 (r,c)(r, c)(r,c) 中的 rrr 记作行,ccc 记作列。矩阵的每个位置都有一个颜色,有黑色和白色两种,我们将黑色记作 #
, 将白色记作 .
您会收到 QQQ 个有关此网格的问题,因此请逐一回答。在第 i(1≤i≤Q)i(1 \le i \le Q)i(1≤i≤Q)个问题中,给出了整数Ui,Di,Li,RiU_i, D_i, L_i, R_iUi,Di,Li,Ri,请回答每个问题的答案。
在对所有不满足 Ui≤r≤DiU_i \le r \le D_iUi≤r≤Di Li≤c≤RiL_i \le c \le R_iLi≤c≤Ri 的位置 (r,c)(r, c)(r,c) 进行着色黑色后, 将以下操作连续执行最多次数。
选择一对整数 (r,c)(r, c)(r,c) 要求 (r,c),(r,c+1),(r+1,c),(r+1,c+1)(r, c), (r, c + 1), (r + 1, c), (r + 1, c + 1)(r,c),(r,c+1),(r+1,c),(r+1,c+1) 全是白色,然后将他们其中一个染成黑色。
独立解决每个问题。换句话说,每个单元格的颜色都重置为每个问题的初始状态。
数据范围
2≤N≤5002 \le N \le 5002≤N≤500
1≤Q≤1051 \le Q \le 10^51≤Q≤105
Sr,cS_{r, c}Sr,c 是 #
和 .
其中之一
1≤Ui<Di≤N1 \le U_i < D_i \le N1≤Ui<Di≤N
1≤Li<Ri≤N1 \le L_i < R_i \le N1≤Li<Ri≤N
本题输入全部为整数
输入格式
输入第一行是一个整数 NNN 和 QQQ
第二行到第 N+1N+1N+1 行,每行 NNN 个字符,表示输入一个矩阵 SSS
第 N+2N+2N+2 行到 N+1+QN + 1 + QN+1+Q 行,每行输入四个整数 Ui,Di,Li,RiU_i,D_i,L_i,R_iUi,Di,Li,Ri
输出格式
输出 QQQ 行,每行表示第 iii 个问题的答案
输入样例1
5 4
..#.#
.....
#....
...#.
.#...
1 3 1 3
3 5 3 5
2 3 1 4
1 5 1 5
输出样例1
2
0
2
5
输入样例2
7 6
#.#....
.....#.
.......
.#..#.#
#....#.
.......
....##.
1 3 2 7
4 6 1 6
6 7 2 7
3 5 4 6
1 6 2 4
1 7 1 7
输出样例2
4
4
2
0
6
13
思路
这道题一看就 没有思路 很神奇。
我们发现题目要将输入的矩阵构造成一个新的矩阵,但是 NNN 可能高达 500500500 如果每个询问都是使用构造法,那么我们发现时间复杂度是 O(n2)O(n^2)O(n2),但是 qqq 可能达到 10510^5105 如果使用构造法就算再怎么优化,也是不可行的
因为时间复杂度到达了 O(n2×q)O(n^2 \times q)O(n2×q) 最坏为 500×500×105=2.5×1010500 \times 500 \times 10^5 = 2.5 \times 10^{10}500×500×105=2.5×1010
已经超过了 10810^8108 不可行 。
但是暴力不可行,但是我们可以通过暴力找规律,
我们首先制造一个输出将矩阵改为题目输入的矩阵的代码
#define FOR(i, a, b) for (int i = (a); i < (b); i++)
#define REP(i, a, b) for (int i = (a); i <= (b); i++)int n, q;
char Map[512][512];
char tmp[512][512];void solve(int u1, int d1, int l1, int r1)
{REP(i, 1, n) {REP(j, 1, n) { tmp[i][j] = Map[i][j];if (i < u1 || i > d1 || j < l1 || j > r1) tmp[i][j] = '#';putchar(tmp[i][j]);}putchar('\n');}
}
当输入为样例 1 中的 1 3 1 3
时,我们发现代码输出了
..###
...##
#..##
#####
#####
作者在比赛的时候发现了如下的规律。
因为题意是计算正方形的个数,我们不管修改,直接计算题目由.
组成的正方形的个数。
我们发现有 2 个,和答案完全吻合,作者又测试了很多数据,都是这样的,这启示我们写下下面的代码:
#include <stdio.h>
#include <vector>
#include <algorithm>
#include <string>
#include <unordered_set>using namespace std;#define FOR(i, a, b) for (int i = (a); i < (b); i++)
#define REP(i, a, b) for (int i = (a); i <= (b); i++)typedef long long ll;int n, q;
char Map[512][512];
char tmp[512][512];void solve(int u1, int d1, int l1, int r1)
{REP(i, 1, n) REP(j, 1, n) { tmp[i][j] = Map[i][j];if (i < u1 || i > d1 || j < l1 || j > r1) tmp[i][j] = '#';}int cnt = 0;REP(i, 1, n) {REP(j, 1, n) if (tmp[i][j] == '.' && tmp[i][j + 1] == '.' && tmp[i + 1][j] == '.' && tmp[i + 1][j + 1] == '.') cnt++;}printf("%d\n", cnt);
}int main()
{scanf("%d %d\n", &n, &q);REP(i, 1, n) REP(j, 1, n) scanf(" %c", &Map[i][j]);while (q --) {int u1, d1, l1, r1;scanf("%d%d%d%d", &u1, &d1, &l1, &r1);solve(u1, d1, l1, r1);}return 0;
}
然后直接 TLE
了,但是正确了 555 个测试点,其它全部超时。
我们考虑改进这个代码,我们可以使用一次预处理,计算出每个位置的 2×22 \times 22×2 的正方形的个数.
这启示我们使用二维前缀和实现,首先一次预处理,再单次查询即可。
不会二维前缀和的点这里: link
代码:
作者连二位前缀和都调了很久
#include <stdio.h>
#include <vector>
#include <algorithm>
#include <string>using namespace std;#define FOR(i, a, b) for (int i = (a); i < (b); i++)
#define REP(i, a, b) for (int i = (a); i <= (b); i++)typedef long long ll;int n, q;
char Map[512][512];
char tmp[512][512];
int TmpCount[512][512];void solve(int u1, int d1, int l1, int r1)
{
// printf("%d %d %d %d\n", TmpCount[d1][r1], TmpCount[r1][l1], TmpCount[u1][d1], TmpCount[u1][l1]);printf("%d\n", TmpCount[d1][r1] - TmpCount[d1][l1] - TmpCount[u1][r1] + TmpCount[u1][l1]);
}int main()
{scanf("%d %d\n", &n, &q);REP(i, 1, n) REP(j, 1, n) scanf(" %c", &Map[i][j]);REP(i, 1, n) REP(j, 1, n) {TmpCount[i][j] = TmpCount[i - 1][j] + TmpCount[i][j - 1] - TmpCount[i - 1][j - 1];if (Map[i - 1][j - 1] == '.' && Map[i][j - 1] == '.' && Map[i - 1][j] == '.' && Map[i][j] == '.') TmpCount[i][j] ++;
// printf("i = %d j = %d Count[i][j] = %d\n", i, j, TmpCount[i][j]);}while (q --) {int u1, d1, l1, r1;scanf("%d%d%d%d", &u1, &d1, &l1, &r1);solve(u1, d1, l1, r1);}return 0;
}
时间复杂度分析
AC 代码的时间复杂度为预处理 O(n2)O(n^2)O(n2) 单词查询为 O(1)O(1)O(1)
所以这个代码的时间复杂度为 O(n2+q)O(n^2 + q)O(n2+q) 完全可行