1. 程式人生 > 其它 >【ybtoj】【二維Hash】對稱正方形

【ybtoj】【二維Hash】對稱正方形

題意

題解

這題洛谷上有,還是個紫題

根據對稱的定義,再看看範圍,容易想到二分答案(Hash好像經常和二分結合在一起)

二分部分

但是具體怎麼二分?

首先確定,二分成立需要單調性,所以一定是從中心點二分,但是當正方形為奇數的時候中心點在中央的格子,那麼偶數呢?

實際上,邊長為偶數的時候正方形的重心在一個格點(就是一個點,不是格子)

概括一下:

  • 對於長度為奇數的正方形,以格子(一個1*1的正方形)為中心二分最遠符合條件的長度
  • 對於長度為偶數的正方形,以格點(就是一個點)為中心二分最遠符合條件的長度

那麼二分的 check 函式就可以用二維Hash來O(1)判斷

二維Hash部分

二維Hash的作用就是判斷矩陣是否相同

就是橫向和縱向分別算兩次Hash值(base1,base2取不同的值),但是第二次Hash和第一次Hash略有不同,具體見程式碼

對於Hash值的查詢,類似二維字首和,下面貼的程式碼中 mi1,base1對應縱座標 y ,mi2,base2對應橫座標 x,記住這個對應關係基本就不會寫錯

PS:這道題卡大部分模數1e9+7(0pts),1e9+9(20pts),98244353(0pts),然而用 ull 自然溢位就沒事(100pts)

程式碼

最大子矩陣
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long 
const int INF = 0x3f3f3f3f,N = 1e3+10,base1 = 233,base2 = 133;
const ll mod = 1e9+9; 
ull mi1[N<<1],mi2[N<<1],sum[N<<1][N<<1];
int len,a[N<<1][N<<1],n,m;
int ans;
void init()
{
	mi1[0]=mi2[0]=1;
	//這裡所有的n,m不要忘記×2 
	for(int i=1;i<=m<<1;i++) mi1[i]=(mi1[i-1]*base1);
	for(int i=1;i<=n<<1;i++) mi2[i]=(mi2[i-1]*base2);
	for(int i=1;i<=n<<1;i++)
		for(int j=1;j<=m<<1;j++)	 
			sum[i][j]=sum[i][j-1]*base1+a[i][j];
	for(int i=1;i<=n<<1;i++)
		for(int j=1;j<=m<<1;j++)	 
			sum[i][j]+=sum[i-1][j]*base2;//注意這裡是+= 
}
inline ll Hash(int xa,int ya,int xb,int yb)
{
	return sum[xb][yb]-sum[xa-1][yb]*mi2[xb-xa+1]-
		   sum[xb][ya-1]*mi1[yb-ya+1]+
		   sum[xa-1][ya-1]*mi1[yb-ya+1]*mi2[xb-xa+1];
}
inline bool check(int xa,int ya,int xb,int yb)
{
	return Hash(xa,ya,xb,yb)==Hash((n<<1)-xb+1,ya,(n<<1)-xa+1,yb)&&
		   Hash(xa,ya,xb,yb)==Hash(xa,(m<<1)-yb+1,xb,(m<<1)-ya+1);//+1要想好 
}
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]);			
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			a[i+n][j]=a[n-i+1][j];
			a[i][j+m]=a[i][m-j+1]; 
		}
	init();//忘記呼叫init()還調了半天 
//	for(int i=1;i<=n<<1;i++)
//	{
//		for(int j=1;j<=m<<1;j++)	
//			printf("%lld ",sum[i][j]);
//		puts("");
//	}	
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			//對於長度為奇數的正方形,以格子(一個1*1的正方形)為中心二分最遠符合條件的長度
			int l=0,r=max(n,m);
			while(l<r)
			{
				int mid=(l+r+1)>>1;
				if(i-mid>=1&&j-mid>=1&&i+mid<=n&&j+mid<=m&&check(i-mid,j-mid,i+mid,j+mid)) l=mid;
				else r=mid-1;
			}
			//printf("#1:(%d,%d,%d,%d)\n",i-l,j-l,i+l,j+l);
			ans+=l+1;
			//對於長度為偶數的正方形,以格點(就是一個點)為中心二分最遠符合條件的長度
			l=0,r=max(n,m);
			while(l<r)
			{
				int mid=(l+r+1)>>1;
				if(i-mid+1>=1&&j-mid+1>=1&&i+mid<=n&&j+mid<=m&&check(i-mid+1,j-mid+1,i+mid,j+mid)) l=mid;
				else r=mid-1;
			}
			//printf("#2:(%d,%d,%d,%d)\n",i-l+1,j-l+1,i+l,j+l);
			ans+=l;
			
		}
	printf("%d\n",ans); 
	return 0;
}