题意:
有一个全由数字组成的矩阵,你要找到一个最大的子正方形,且这个正方形每一行每一列都是回文串。
解法一:
先假设正方形的边长len为奇数,那么对于中心点(x,y),所有的(x-len/2,y)~(x+len/2,y)(y同理)都要是至少回文半径为len/2+1的回文串。如果我们事先用manacher求出所有的行和列的回文半径的话,用rmq就知道(x-len/2,y)~(x+len/2,y)是否满足要求。据说不用rmq直接一个个验证更快。
len是偶数咋办?像manacher一样,填充原矩阵,就不用管奇偶的问题了,但是中心点所在的行列要么都是填充的,要么都不是填充的。
//Time:296ms
//Memory:17960KB
//Length:2940B
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#define MAXN 620
#define INF 1007
using namespace std;
int arr[MAXN][MAXN],tarr[MAXN];
short rad[2][MAXN][MAXN];
short rmq[2][MAXN][MAXN][10];
int ilog[MAXN];
void manacher(int a[],short r[],int len)
{
for(int i=0;i<len;++i) r[i]=0;
r[0]=1;
for(int i=1,j=0,k;i<len;)
{
while(a[i-j-1]==a[i+j+1]) ++j;
r[i]=j;
for(k=1;k<=j&&r[i-k]!=r[i]-k;++k)
r[i+k]=min((int)r[i-k],r[i]-k);
i+=k;
j=max(0,j-k);
}
}
void calrmq(int a[],short r[][10],int len)
{
int fe=log(len)/log(2)+1;
for(int i=0;i<len;++i) r[i][0]=a[i];
for(int i=1,j=1;i<fe;++i,j*=2)
for(int k=0;k<len;++k)
if(k+j<len)
r[k][i]=min(r[k][i-1],r[k+j][i-1]);
else r[k][i]=r[k][i-1];
}
bool check(int x,int y,int len)
{
int fe=ilog[len*2],ji;
short tlen=INF;
ji=1<<fe;
tlen=min(tlen,min(rmq[0][y][x-len][fe],rmq[0][y][x+len-ji+1][fe]));
tlen=min(tlen,min(rmq[1][x][y-len][fe],rmq[1][x][y+len-ji+1][fe]));
return tlen>=len;
}
int main()
{
//freopen("/home/moor/Code/input","r",stdin);
int ncase,n,m,ans,nn,mm,tmp;
scanf("%d",&ncase);
for(int i=1;i<MAXN;++i)
ilog[i]=log(i*1.0)/log(2.0);
while(ncase--)
{
ans=0;
scanf("%d%d",&n,&m);
nn=n*2+3,mm=m*2+3;
memset(arr,-1,sizeof(arr));
for(int i=0;i<n;++i)
for(int j=0;j<m;++j)
scanf("%d",&arr[(i+1)*2][(j+1)*2]);
n=n*2+3,m=m*2+3;
for(int i=0;i<m;++i)
arr[0][i]=-2,arr[n-1][i]=-3;
for(int i=0;i<n;++i)
arr[i][0]=-4,arr[i][m-1]=-5;
arr[0][0]=-10,arr[n-1][0]=-11,arr[0][m-1]=-12,arr[n-1][m-1]=-13;
for(int i=0;i<n;++i)
manacher(arr[i],rad[0][i],m);
for(int i=0;i<m;++i)
{
for(int j=0;j<n;++j) tarr[j]=arr[j][i];
manacher(tarr,rad[1][i],n);
}
for(int i=0;i<m;++i)
{
for(int j=0;j<n;++j) tarr[j]=rad[0][j][i];
calrmq(tarr,rmq[0][i],n-1);
}
for(int i=0;i<n;++i)
{
for(int j=0;j<m;++j) tarr[j]=rad[1][j][i];
calrmq(tarr,rmq[1][i],m-1);
}
for(int i=1;i<n;++i)
for(int j=1;j<m;++j)
{
if((j%2==1)&&(i%2==0)||(j%2==0)&&(i%2==1)) continue;
int l=1,r=min(min(i-1,n-i),min(j-1,m-j)),mid;
while(l<r)
{
if(ans>=r) break;
mid=(l+r+1)/2;
if(check(i,j,mid)) l=mid;
else r=mid-1;
}
ans=max(ans,l);
}
printf("%d\n",ans);
}
return 0;
}
解法二:
二分(奇偶)+hash判断是否对称,其实好暴力的,但是竟然比上面的快,比赛没敢敲==
代码:
//time: 109ms
//length: 2299B
//memory: 3504K
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 305
int n,m,a[maxn][maxn];
long long int p[maxn],hash_r[2][maxn][maxn],hash_c[2][maxn][maxn];
void hash()
{
for (int i=1;i<=n;i++)
{
for (int j=1;j<=m;j++)
hash_r[0][i][j]=hash_r[0][i][j-1]*131+a[i][j];
for (int j=m;j>=1;j--)
hash_r[1][i][j]=hash_r[1][i][j+1]*131+a[i][j];
}
for (int i=1;i<=m;i++)
{
for (int j=1;j<=n;j++)
hash_c[0][j][i]=hash_c[0][j-1][i]*131+a[j][i];
for (int j=n;j>=1;j--)
hash_c[1][j][i]=hash_c[1][j+1][i]*131+a[j][i];
}
}
long long int calr0(int r,int x,int y) {return hash_r[0][r][y]-hash_r[0][r][x-1]*p[y-x+1];}
long long int calr1(int r,int x,int y) {return hash_r[1][r][x]-hash_r[1][r][y+1]*p[y-x+1];}
long long int calc0(int c,int x,int y) {return hash_c[0][y][c]-hash_c[0][x-1][c]*p[y-x+1];}
long long int calc1(int c,int x,int y) {return hash_c[1][x][c]-hash_c[1][y+1][c]*p[y-x+1];}
bool check(int l)
{
for (int i=1;i+l-1<=n;i++)
for (int j=1;j+l-1<=m;j++)
{
bool flag=true;
for (int k=i;k<=i+l-1&&flag;k++)
if (calr0(k,j,j+l/2-1)!=calr1(k,j+l/2+l%2,j+l-1))
flag=false;
for (int k=j;k<=j+l-1&&flag;k++)
if (calc0(k,i,i+l/2-1)!=calc1(k,i+l/2+l%2,i+l-1)) flag=false;
if (flag) return true;
}
return false;
}
int main()
{
//freopen("/home/christinass/code/in.txt","r",stdin);
p[0]=1;
for (int i=1;i<=300;i++) p[i]=p[i-1]*131;
int cas;
scanf("%d",&cas);
while (cas--)
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
scanf("%d",&a[i][j]);
hash();
int l=0,r=n/2,ans=1;
while (l<=r)
{
int mid=(l+r)>>1;
if (check(2*mid+1)) ans=2*mid+1,l=mid+1;
else r=mid-1;
}
l=(ans+1)/2,r=n/2;
while (l<=r)
{
int mid=(l+r)>>1;
if (check(2*mid)) ans=2*mid,l=mid+1;
else r=mid-1;
}
printf("%d\n",ans);
}
return 0;
}