2020 CCPC 秦皇岛 B.Bounding Wall

我搬运我自己应该算原创吧

题目

题意

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 表示查询

查询如下:

找一个面积最大的矩形边框(平行与坐标轴), 满足:

  1. 边框上的点都是 0 0 0
  2. ( x , y ) (x, y) (x,y)在边框上

1 ≤ T , n , m , q ≤ 1 0 3 1 \le T, n, m, q \le 10^3 1T,n,m,q103

题解

先来看询问.

首先很容易发现, 我们可以枚举 ( 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 %}}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值