【每日一题】Namomo Spring Camp 2022 Div1 # “Z“型矩阵

"Z"型矩阵

题目链接

题意

对一个只包含 . . . z z z 的矩阵,当以下条件满足时 :

  1. 该矩阵的行数列数相等。
  2. 该矩阵的第一行与最后一行的字符全是 z z z
  3. 该矩阵从右上角到左下角的对角线上的字符全是 z z z

我们称其为 z z z 矩阵 现在给定一个 n ∗ m n * m nm 的矩阵,请你计算它有多少个子矩阵是 z z z 矩阵

n , m n,m n,m 分别表示矩阵的行数和列数

1 ≤ \leq n , m n,m nm ≤ \leq 3 e 3 3e^3 3e3

思路

首先我们可以通过前缀和的思想,先预处理出来左边,右边和斜着的数量。

有了这些之后,发现给的数据范围很大,复杂度必须要在 n l o g n nlogn nlogn 才能过,那现在的暴力写法是 n 3 n^3 n3 的,先 O ( n 2 ) O(n^2) O(n2) 枚举 Z Z Z 型的右上角的那个点,然后对左边和斜下角取 m i n min min 操作,然后再枚举斜下角右边的点能不能满足要求。

但这个写法时间复杂度显然是不行的

这里用了树状数组去优化,以每一条斜下角作为一棵树,可以发现同一颗树下面的点 x + y x+y x+y 下标值之和都是一样的,然后就是考虑每个点插入的优先级。

可以发现每个点的优先级是他自己的下标 y y y 值 加上右边 z z z 值的和,枚举的时候从右向左进行枚举

最后树状数组直接查询那段区间和就好了

代码


#include <iostream>
#include <cstdlib>
#include <algorithm>
#include <map>
#include <vector>
#include <queue>
using namespace std;

#define fr first
#define se second 
#define int long long

const int maxn = 3e3 + 10;
int n, m;
char a[maxn][maxn];
int pre[maxn][maxn], lst[maxn][maxn];
int xie[maxn][maxn];
int lz[maxn][maxn];
vector<pair<int, int>>g[maxn];
bool in(int x, int y) {
	if (x >= 1 && x <= n && y >= 1 && y <= m) return 1;
	else return 0;
}
int tree[maxn << 1][maxn];
int ask(int k, int x){
	int ans = 0;
	for (; x; x -= x & -x)ans += tree[k][x];
	return ans;
}
void add(int k, int x, int val){
	for (; x <= m; x += x & -x)tree[k][x] += val;
}
void solve() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++)for (int j = 1; j <= m; j++) cin >> a[i][j];
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			if (a[i][j] == 'z') pre[i][j] = pre[i][j - 1] + 1;
			else pre[i][j] = 0;
		}	
		for (int j = m; j >= 1; j--) {
			if (a[i][j] == 'z')lst[i][j] = lst[i][j + 1] + 1;
			else lst[i][j] = 0;
		}
		for (int j = 1; j <= m; j++) {
			if (a[i][j] == 'z') g[j + lst[i][j] - 1].push_back(make_pair(i, j));
		}
	}

	for (int i = n; i >= 1; i--) {
		for (int j = 1; j <= m; j++) {
			if (a[i][j] == 'z') lz[i][j] = lz[i + 1][j - 1] + 1;
			else lz[i][j] = 0;
		}
	}
	int ans = 0;
	for (int j = m; j >= 1; j--) {
		for (int i = 0; i < g[j].size(); i++) {
			add(g[j][i].fr + g[j][i].se, g[j][i].se, 1);//插的是点的y值的下标
		}
		for (int i = 1; i <= n; i++) {
			if (a[i][j] == 'z') {
				int c = min(pre[i][j], lz[i][j]);
				ans += ask(i + j, j) - ask(i + j, j - c);
			}
		}
	}
	cout << ans << endl;
}


signed main() {

	int t; t = 1;
	while (t--) solve();

	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值