题解--[ZJOI2009]对称的正方形
讲一下 manacher 的做法,这是一个很好的 manacher 练习题。
题目要求上下左右都对称的正方形个数,所以我们得通过上下对称和左右对称两个方面来限制。
我们先用 manacher 算法,得出对于每个格子在行方向上能延伸多远,记为
然后我们发现,如果一个正方形要满足上下对称,那么对于最中间的那一行,在列方向上能延伸的回文串长度肯定要大于等于正方形的边长。左右对称同理。于是我们就可以用二分来得到单看满足上下对称/左右对称,这个正方形的边长最大是多少。
具体来说(以左右对称为例),假设我们现在枚举的整个正方形的中心为
得到两个方向的边长(实际上是中心到边缘的距离)后,因为我们在其中插入了
#include<bits/stdc++.h>
using namespace std;
const int maxn=2005;
int a[maxn][maxn],n,m,lg2[maxn],f[13][maxn],h[maxn][maxn],l[maxn][maxn],p[maxn],hl[maxn][maxn],ll[maxn][maxn],ans;
void solveh(int k) {
int s[maxn],mr=0,mid=0;
memset(p,0,sizeof(p));
for(int i=0; i<=2*m; i++)s[i]=a[k][i];
for(int i=0; i<=2*m; i++) {
if(i<mr)p[i]=min(p[2*mid-i],mr-i);
while(i-p[i]>=1&&i+p[i]<=2*m-1&&s[i-p[i]-1]==s[i+p[i]+1])p[i]++;
if(i+p[i]>mr)mid=i,mr=i+p[i];
h[k][i]=p[i];
}
}
void solvel(int k) {
int s[maxn],mr=0,mid=0;
memset(p,0,sizeof(p));
for(int i=0; i<=2*n; i++)s[i]=a[i][k];
for(int i=0; i<=2*n; i++) {
if(i<mr)p[i]=min(p[2*mid-i],mr-i);
while(i-p[i]>=1&&i+p[i]<=2*n-1&&s[i-p[i]-1]==s[i+p[i]+1])p[i]++;
if(i+p[i]>mr)mid=i,mr=i+p[i];
l[i][k]=p[i];
}
}//manacher
void buildst(int n) {
for(int j=1; j<=lg2[n]; j++)
for(int i=1; i<=n-(1<<j)+1; i++)f[j][i]=min(f[j-1][i],f[j-1][i+(1<<j-1)]);
}
int ask(int l,int r) {
int len=lg2[r-l+1];
return min(f[len][l],f[len][r-(1<<len)+1]);
}//st表处理区间min
int main() {
scanf("%d%d",&n,&m);
for(int i=1; i<=2*max(n,m); i++)lg2[i]=lg2[i-1]+((1<<lg2[i-1]+1)==i);
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++)scanf("%d",&a[i*2-1][j*2-1]);
for(int i=1; i<=2*n-1; i++)solveh(i);
for(int j=1; j<=2*m-1; j++) {
for(int i=1; i<=2*n-1; i++)f[0][i]=h[i][j];
buildst(2*n-1);
for(int i=1; i<=2*n-1; i++) {
int l=1,r=min(i,2*n-i),ans=0;
while(l<=r) {
int mid=l+r>>1;
if(ask(i-mid+1,i+mid-1)>=mid)ans=mid,l=mid+1;
else r=mid-1;
}
hl[i][j]=ans;//左右对称
}
}
for(int i=1; i<=2*m-1; i++)solvel(i);
for(int i=1; i<=2*n-1; i++) {
for(int j=1; j<=2*m-1; j++)f[0][j]=l[i][j];
buildst(2*m-1);
for(int j=1; j<=2*m-1; j++) {
int l=1,r=min(j,2*m-j),ans=0;
while(l<=r) {
int mid=l+r>>1;
if(ask(j-mid+1,j+mid-1)>=mid)ans=mid,l=mid+1;
else r=mid-1;
}
ll[i][j]=ans;//上下对称
}
}
for(int i=1; i<=2*n-1; i++)
for(int j=1; j<=2*m-1; j++) {
int v=min(hl[i][j],ll[i][j]);
if((i&1)&&(j&1))ans+=(v+1)/2;
else if(!(i&1)&&!(j&1))ans+=v/2;//统计答案
}
printf("%d",ans);
return 0;
}