【ybt高效进阶2-2-3】【luogu P2601】对称正方形

21 篇文章 0 订阅

对称正方形

题目链接:ybt高效进阶2-2-3 / luogu P2601

题目大意

给你一个矩阵,问你这个矩阵中上下对称且左右对称的正方形子矩阵的个数。

思路

这道题就是模拟很烦而已。

首先,很明显要用 hash 来做,因为是矩形,是二维的。那就要用二维的 hash。
(具体就是搞两个质数,先把每一行用一个质数 hash 一下,然后在一列一列按另一个质数 hash)
`
至于得到一个区间的 hash 值(假设是 [ l x , l y ] ∼ [ r x , r y ] [lx,ly]\sim[rx,ry] [lx,ly][rx,ry],两个质数分别是 z 1 , z 2 z1,z2 z1,z2),就是:
h a s h r x , r y − h a s h r x , l y − 1 × z 1 r y − l y + 1 − h a s h l x − 1 , r y × z 2 r x − l x + 1 + h a s h l x − 1 , l y − 1 × z 1 r y − l y + 1 × z 2 r x − l x + 1 hash_{rx,ry}-hash_{rx,ly-1}\times z1^{ry-ly+1}-hash_{lx-1,ry}\times z2^{rx-lx+1}+hash_{lx-1,ly-1}\times z1^{ry-ly+1}\times z2^{rx-lx+1} hashrx,ryhashrx,ly1×z1ryly+1hashlx1,ry×z2rxlx+1+hashlx1,ly1×z1ryly+1×z2rxlx+1

那你考虑一下一个左右上下对称的正方形要满足什么特点。
那很明显,对称就是按着对称轴翻转过来它还是一样的。
那就是这个正方形左右反过来,上下反过来所形成的图形和原来都一样。

那你就构造出最大的矩形的两个翻转图形,然后看看原来的位置应该变道哪里。
原来是 [ x , y ] [x,y] [x,y](假设),那左右翻转就是 [ x , m − y + 1 ] [x,m-y+1] [x,my+1],上下翻转就是 [ n − x + 1 , y ] [n-x+1,y] [nx+1,y]

那我们再看矩形翻转之后的位置变化。
假设原来是 [ l x , l y ] ∼ [ r x , r y ] [lx,ly]\sim[rx,ry] [lx,ly][rx,ry],那左右翻转的就是 [ l x , m − r y + 1 ] ∼ [ r x , m − r x + 1 ] [lx,m-ry+1]\sim[rx,m-rx+1] [lx,mry+1][rx,mrx+1],上下翻转的就是 [ n − r x + 1 , l y ] ∼ [ n − l x + 1 , l y ] [n-rx+1,ly]\sim[n-lx+1,ly] [nrx+1,ly][nlx+1,ly]

那我们就可以枚举矩阵的中心点,然后我们可以把这些矩阵分成两种。要么是长度是奇数的,要么是长度是偶数的。
你可以发现,对于某一种矩阵,如果你对于这个中心的长度为 x x x 的矩阵是对称的,那对于这种矩阵,这个中心的长度小于等于 x x x 的矩阵都是对称的。

那我们就发现它满足二分的单调性,按我们可以分别把两种矩阵二分出个数。

然后我们就得到答案了。

代码

#include<cstdio>
#include<iostream>
#define di1 1000000007ull
#define di2 1000000009ull
#define ull unsigned long long

using namespace std;

int n, m, a[1001][1001], matrix_up[1001][1001], matrix_left[1001][1001], l, r, mid, ans, tot, lx, ly, tmp;
ull hash[1001][1001], times1[1001], times2[1001], hash_up[1001][1001], hash_left[1001][1001], hash1, hash2, hash3;

bool ch(int rx, int ry, int dis) {
	lx = rx - dis + 1;//这个是普通的矩阵
	ly = ry - dis + 1;
	hash1 = hash[rx][ry] - hash[rx][ly - 1] * times1[dis] - hash[lx - 1][ry] * times2[dis] + hash[lx - 1][ly - 1] * times1[dis] * times2[dis];
	
	tmp = rx;//这个是上下翻转的矩阵
	rx = n - (rx - dis);
	lx = rx - dis + 1;
	ly = ry - dis + 1;
	hash2 = hash_up[rx][ry] - hash_up[rx][ly - 1] * times1[dis] - hash_up[lx - 1][ry] * times2[dis] + hash_up[lx - 1][ly - 1] * times1[dis] * times2[dis];
	
	rx = tmp;//这个是左右翻转的矩阵
	ry = m - (ry - dis);
	lx = rx - dis + 1;
	ly = ry - dis + 1;
	hash3 = hash_left[rx][ry] - hash_left[rx][ly - 1] * times1[dis] - hash_left[lx - 1][ry] * times2[dis] + hash_left[lx - 1][ly - 1] * times1[dis] * times2[dis];
	
	if (hash1 == hash2 && hash1 == hash3) return 1;
	return 0;
}

int main() {
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++) {
			scanf("%d", &a[i][j]);
			matrix_up[n - i + 1][j] = a[i][j];//得到两种翻转的矩阵
			matrix_left[i][m - j + 1] = a[i][j];
		}
	
	times1[0] = 1ull;//求出来后面有用
	for (int i = 1; i <= n; i++)
		times1[i] = times1[i - 1] * di1;
	times2[0] = 1ull;
	for (int i = 1; i <= m; i++)
		times2[i] = times2[i - 1] * di2; 
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {//得出矩阵hash值
			hash[i][j] = hash[i][j - 1] * di1 + a[i][j];
			hash_up[i][j] = hash_up[i][j - 1] * di1 + matrix_up[i][j];
			hash_left[i][j] = hash_left[i][j - 1] * di1 + matrix_left[i][j];
			
		}
		times1[i] = times1[i - 1] * di1;
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			hash[i][j] += hash[i - 1][j] * di2;
			hash_up[i][j] += hash_up[i - 1][j] * di2;
			hash_left[i][j] += hash_left[i - 1][j] * di2;
		}
	}
	
	for (int i = 1; i <= n; i++)//枚举中心在哪里
		for (int j = 1; j <= m; j++) {
			ans = 0;
			l = 1;
			r = min(min(i, n - i + 1), min(j, m - j + 1));
			while (l <= r) {//长度是奇数
				mid = (l + r) >> 1;
				
				if (i - mid + 1 < 1 || i + mid - 1 > n || j - mid + 1 < 1 || j + mid - 1 > m) {
					r = mid - 1;
					continue;
				}
				
				if (ch(i + mid - 1, j + mid - 1, mid * 2 - 1)) {
					ans = mid;
					l = mid + 1;
				}
				else r = mid - 1;
			}
			tot += ans;
			
			ans = 0;
			l = 1;
			r = min(min(i, n - i), min(j, m - j));
			while (l <= r) {//长度是偶数
				mid = (l + r) >> 1;
				
				if (i - mid + 1 < 1 || i + mid > n || j - mid + 1 < 1 || j + mid > m) {
					r = mid - 1;
					continue;
				}
				
				if (ch(i + mid, j + mid, mid * 2)) {
					ans = mid;
					l = mid + 1;
				}
				else r = mid - 1;
			}
			tot += ans;
		}
	
	printf("%d", tot);
	
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值