题意
T T T组测试. 给出 n × m n \times m n×m的 01 01 01矩阵 M M M. q q q次操作.
1 x y
将矩阵中 ( x , y ) (x, y) (x,y)取反2 x y
表示查询
查询如下:
找一个面积最大的矩形边框(平行与坐标轴), 满足:
- 边框上的点都是 0 0 0
- ( x , y ) (x, y) (x,y)在边框上
1 ≤ T , n , m , q ≤ 1 0 3 1 \le T, n, m, q \le 10^3 1≤T,n,m,q≤103
题解
先来看询问.
首先很容易发现, 我们可以枚举 ( x , y ) (x, y) (x,y)在矩阵的哪一条边上, 这样只需要做 4 4 4次同样的算法即可.
下面仅仅考虑 ( x , y ) (x, y) (x,y)在下边的做法.
通过 O ( n 2 ) O(n^2) O(n2)的预处理, 可以很快知道 ( x , y ) (x, y) (x,y)左右两边最近的 1 1 1. 设为 L L L和 R R R
然后考虑枚举上边所在的行 u u u. 现在只需要知道两根竖直边在哪一列上即可. j j j列能够作为竖直边的充要条件是 j ∈ ( L , R ) , ∀ i ∈ [ u , x ] , M i , j = 0 j \in (L, R), \forall i \in [u, x], M_{i, j} = 0 j∈(L,R),∀i∈[u,x],Mi,j=0. 显然要使面积最大, 应该要让左竖直边的下标 j l j_l jl尽量小, 右竖直边的下标 j r j_r jr尽量大.
对于一个 1 1 1, 只要他行 ( u , x ] (u, x] (u,x]中, 他的行号其实是不重要的. 如果 i ( i ∈ [ u , x ] ) i(i \in[u, x]) i(i∈[u,x])行在 ( L , R ) (L, R) (L,R)之间出现了 1 1 1, 我们其实可以直接认为这个 1 1 1在 u + 1 u+1 u+1行. 一种考虑方式是尝试从大到小枚举 u u u, 让 u + 1 u+1 u+1以下的行的信息"压"到 u + 1 u+1 u+1上, 如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vg8lUdrp-1619673423168)(/img/vp-2020-ccpc-qinhuangdao-site/push-to-u.jpg)]
然后在 u u u行找左右两边最近的 1 1 1, 见上图(右), 然后看 u + 1 u+1 u+1行对应的左右位置, 如果是 0 0 0(右图右箭头下方), 那么就找到了一侧的竖直边, 上图即 c 2 c_2 c2; 如果是 1 1 1(右图左箭头下方), 那么还得找 u + 1 u+1 u+1行 c 1 + 1 c_1 + 1 c1+1所在"连通块"的最右端(或最左端, 看情况)
"连通块"都出来了, 还不知道用并查集吗? 并查集维护一下每个连通块最左边和最右边的点的下标即可, 详见代码.
不过, 我们不能直接枚举每行的 1 1 1, 否则复杂度会是 O ( n 2 ) O(n^2) O(n2), 加上查询总复杂度有 O ( n 3 ) O(n^3) O(n3), 过不了.
从大到小做的话, 可以发现, 如果一列上有多个 1 1 1, 只需考虑低的 1 1 1即可. 可以在 O ( n 2 ) O(n^2) O(n2)内预处理出在 ( x , y ) (x, y) (x,y)上方离 ( x , y ) (x, y) (x,y)最近的 1 1 1的位置. 只考虑这些 1 1 1, 就可以在 O ( n + m ) = O ( n ) O(n + m) = O(n) O(n+m)=O(n)的时间完成了.
具体来说, 对每一行开一个池子(vector) pool[]
, 对于每一个在
(
L
,
R
)
(L, R)
(L,R)中的
j
j
j, 快速查询到
(
x
,
j
)
(x, j)
(x,j)点上方的
1
1
1的行号
i
i
i, 然后把
j
j
j压入pool[i]
. 当枚举到
i
i
i行的时候, 把 pool[i]
中的
j
j
j拿出来处理就行.
到这里, 询问的关键算法就结束了. 复杂度 O ( n 2 ) O(n^2) O(n2)
然后需要把矩阵旋转 3 3 3次, 每一次跑一遍, 答案取最大即可.
但是! 不能"在线"旋转, 否则每查询一次, 都要花 O ( n 2 ) O(n^2) O(n2)的时间旋转, 这样总复杂度就是 O ( n 3 ) O(n^3) O(n3)了. 可以离线, 即对每个方向的矩阵跑一遍所有询问, 记录答案. 转了以后重新跑一遍所有询问, 更新答案. 这样总共只需要转 3 3 3下, 旋转的复杂度 O ( n 2 ) O(n^2) O(n2)不会加在询问 O ( n ) O(n) O(n)上.
注意旋转了以后, 操作中的 ( x , y ) (x, y) (x,y)也需要旋转到相应的点 ( x ′ , y ′ ) (x', y') (x′,y′)上
每次旋转了以后, 也可以用
O
(
n
2
)
O(n^2)
O(n2)的时间重新预处理数组l[][], r[][], up[][]
了.
最后看简单一点的修改.
矩阵中的值直接取反, 一个点的值改变, 只会改变
x
′
x'
x′行的l[][], r[][]
和
y
′
y'
y′列的up[][]
, 直接花
O
(
n
)
O(n)
O(n)重新处理该行或列即可.
总复杂度 O ( n 2 ) O(n^2) O(n2)
{{% code %}}
const int maxn = 1e3+10;
struct Question {
int op, x, y;
};
int t, n, m, q, N, M;
char str[maxn][maxn];
int layout[maxn][maxn], ans[maxn];
int l[maxn][maxn], r[maxn][maxn], up[maxn][maxn];
int fa[maxn], mark[maxn], mx[maxn], mn[maxn];
vector<int> pool[maxn];
vector<Question> qs;
void UpdAns(int id, int a) {
ans[id] = max(ans[id], a);
}
int Find(int x) {
return x == fa[x] ? x : fa[x] = Find(fa[x]);
}
void Union(int x, int y) {
x = Find(x), y = Find(y);
fa[x] = y;
mn[y] = min(mn[y], mn[x]);
mx[y] = max(mx[y], mx[x]);
}
void sol(int id, int rot) {
int op = qs[id].op, x = qs[id].x, y = qs[id].y;
if (rot == 1) {
int xx = x, yy = y;
x = yy, y = N - xx + 1;
}
else if (rot == 2) {
x = N - x + 1, y = M - y + 1;
}
else if (rot == 3) {
int xx = x, yy = y;
x = M - yy + 1, y = xx;
}
if (op == 1) {
layout[x][y] ^= 1;
//只需要维护当前行或者列的值
for (int j = 1; j <= m; j++)
l[x][j] = layout[x][j] ? j : l[x][j-1];
for (int j = m; j; j--)
r[x][j] = layout[x][j] ? j : r[x][j+1];
for (int i = 1; i <= n; i++)
up[i][y] = layout[i][y] ? i : up[i-1][y];
}
else {
if (!layout[x][y]) {
//L, R是最左, 最右的不可放置点
int L = l[x][y], R = r[x][y];
UpdAns(id, R - L - 1);
//清空pool
for (int i = 1; i < x; i++)
pool[i].clear();
//计算pool, 初始化fa, mark
for (int j = L + 1; j <= R - 1; j++)
pool[up[x][j]].push_back(j);
for (int j = L; j <= R; j++) {
mark[j] = 0;
fa[j] = mn[j] = mx[j] = j;
}
mark[L] = mark[R] = 1;
//枚举上边
for (int i = x - 1; i; i--) {
//更新并查集, 加入当前行的不可放置点
for (auto j : pool[i]) if (!mark[j]) {
mark[j] = 1;
if (mark[j-1])
Union(j, j-1);
if (mark[j+1])
Union(j, j+1);
}
int cur_L = max(L, l[i][y]);
cur_L = Find(cur_L);
cur_L = mx[cur_L];
int cur_R = min(R, r[i][y]);
cur_R = Find(cur_R);
cur_R = mn[cur_R];
if (cur_L < y && y < cur_R)
UpdAns(id, (cur_R - cur_L - 1) * (x - i + 1));
}
}
}
}
//初始化l, r, up
void Init() {
for (int i = 1; i <= n; i++) {
l[i][0] = 0;
for (int j = 1; j <= m; j++)
l[i][j] = layout[i][j] ? j : l[i][j-1];
}
for (int i = 1; i <= n; i++) {
r[i][m+1] = m+1;
for (int j = m; j; j--)
r[i][j] = layout[i][j] ? j : r[i][j+1];
}
for (int j = 1; j <= m; j++) {
up[0][j] = 0;
for (int i = 1; i <= n; i++)
up[i][j] = layout[i][j] ? i : up[i-1][j];
}
}
//旋转layout
void Rotate(int rot) {
for (int i = 1; i <= N; i++)
for (int j = 1; j <= M; j++) {
if (rot == 1)
layout[j][N - i + 1] = str[i][j] == '.';
else if (rot == 2)
layout[N - i + 1][M - j + 1] = str[i][j] == '.';
else if (rot == 3)
layout[M - j + 1][i] = str[i][j] == '.';
}
swap(n, m);
}
int main() {
scanf("%d", &t);
for (int kase = 1; kase <= t; kase++) {
scanf("%d%d%d", &n, &m, &q);
N = n, M = m;
for (int i = 1; i <= n; i++)
scanf("%s", str[i] + 1);
qs.clear();
for (int qq = 1; qq <= q; qq++) {
int op, x, y;
scanf("%d%d%d", &op, &x, &y);
qs.push_back(Question{op, x, y});
}
int sz = qs.size();
for (int i = 0; i < sz; i++)
ans[i] = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
layout[i][j] = str[i][j] == '.';
Init();
for (int i = 0; i < sz; i++)
sol(i, 0);
for (int cnt = 1; cnt <= 3; cnt++) {
Rotate(cnt);
Init();
for (int i = 0; i < sz; i++)
sol(i, cnt);
}
printf("Case #%d:\n", kase);
for (int i = 0; i < sz; i++) if (qs[i].op == 2)
printf("%d\n", ans[i]);
}
return 0;
}
{{% /code %}}