题目大意
给出一个 n × m n\times m n×m 的矩阵,求矩阵中上下左右对称的正方形子矩阵的个数。
思路
首先这道题暴力去搞肯定会超时,
因为主要是枚举和判断两大部分耗时,所以想到了用二维哈希去做。
此题需要寻找上下左右对称的正方形子矩阵,所以要哈希三次,
分别是原矩阵,上下颠倒的矩阵和左右颠倒的矩阵。
达到 O ( 1 ) O(1) O(1) 的判断复杂度后枚举还是会超时 ( O ( n 3 ) O(n^3) O(n3) 的复杂度),因此考虑优化掉一个 n n n。
回到暴力,一般的暴力都是枚举每一个点,然后从枚举到的点发散判断有否符合条件的矩阵,
发现可以用二分来优化发散步骤的时间,最后时间复杂度是 O ( n 2 log 2 n ) O(n^2\log_2{n}) O(n2log2n)。
大体思路解决后,思考怎样发散。
我的思路非常清奇,枚举矩阵右下角的点来做,
可是这样不能满足二分到的范围以内都是上下左右对称的正方形子矩阵啊?
所以需要由右下角的点把二分范围扩大一倍来作判断条件,以中心点为中心。
以此来确保计算的正确性。
其实也就是由右下角的点来确定中心点,异曲同工。
判断部分就是把构造出的三个哈希矩阵直接对比(当然是数值的比较),这里不再赘述。
二分要分奇偶,因为奇数和偶数有统计上的差异。
代码
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
unsigned long long a[1010][1010],b[1010][1010],c[1010][1010];
unsigned long long f1[1000010],f2[1000010];
int n,m,ans;
bool check(int mid,int i,int j)
{
if(i-mid<0||j-mid<0||i>n||j>m||i<0)
return 0;
int a1=a[i][j]-a[i-mid][j]*f2[mid]-a[i][j-mid]*f1[mid]+a[i-mid][j-mid]*f1[mid]*f2[mid];
int bx=n-(i-mid),by=m-(j-mid);
int b1=b[bx][j]-b[bx-mid][j]*f2[mid]-b[bx][j-mid]*f1[mid]+b[bx-mid][j-mid]*f1[mid]*f2[mid];
int c1=c[i][by]-c[i-mid][by]*f2[mid]-c[i][by-mid]*f1[mid]+c[i-mid][by-mid]*f1[mid]*f2[mid];
//cout<<a1<<" "<<b1<<" "<<c1<<endl;
if(a1==b1&&b1==c1)
return 1;
else
return 0;
}
void yhash()
{
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++)
{
a[i][j]=a[i][j-1]*1000000009ull+a[i][j];
b[i][j]=b[i][j-1]*1000000009ull+b[i][j];
c[i][j]=c[i][j-1]*1000000009ull+c[i][j];
}
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++)
{
a[i][j]=a[i-1][j]*1000000007ull+a[i][j];
b[i][j]=b[i-1][j]*1000000007ull+b[i][j];
c[i][j]=c[i-1][j]*1000000007ull+c[i][j];
}
}
int main()
{
cin>>n>>m;
f1[0]=1ull,f2[0]=1ull;
for(int i=1; i<=max(n,m); i++)
f1[i]=f1[i-1]*1000000009ull,f2[i]=f2[i-1]*1000000007ull;
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++)
{
scanf("%ulld",&a[i][j]);
c[i][m-j+1]=a[i][j]; //左右颠倒
b[n-i+1][j]=a[i][j]; //上下颠倒
//(不明白的手推一下)
}
yhash();
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++)
{
int l=0,r=min(i,j)+1,mid=0,maxx=1;
while(l<=r)//奇数
{
mid=(l+r)/2;
if(check(mid*2-1,i+mid,j+mid)) //扩大到以中心点为中心
l=mid+1,maxx=max(mid*2-1,maxx);
else
r=mid-1;
}
ans=ans+(maxx+1)/2;
maxx=0;
l=0,r=min(i,j);
while(l<=r) //偶数
{
mid=(l+r)/2;
if(check(mid*2,i+mid,j+mid)) //同上
l=mid+1,maxx=max(mid*2,maxx);
else
r=mid-1;
}
ans=ans+(maxx+1)/2;
}
cout<<ans;
return 0;
}