对称正方形
题目链接: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,ry−hashrx,ly−1×z1ry−ly+1−hashlx−1,ry×z2rx−lx+1+hashlx−1,ly−1×z1ry−ly+1×z2rx−lx+1
那你考虑一下一个左右上下对称的正方形要满足什么特点。
那很明显,对称就是按着对称轴翻转过来它还是一样的。
那就是这个正方形左右反过来,上下反过来所形成的图形和原来都一样。
那你就构造出最大的矩形的两个翻转图形,然后看看原来的位置应该变道哪里。
原来是 [ x , y ] [x,y] [x,y](假设),那左右翻转就是 [ x , m − y + 1 ] [x,m-y+1] [x,m−y+1],上下翻转就是 [ n − x + 1 , y ] [n-x+1,y] [n−x+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,m−ry+1]∼[rx,m−rx+1],上下翻转的就是 [ n − r x + 1 , l y ] ∼ [ n − l x + 1 , l y ] [n-rx+1,ly]\sim[n-lx+1,ly] [n−rx+1,ly]∼[n−lx+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;
}