[ZJOI2009]对称的正方形 manacher+单调队列

快看,这里有个蒟蒻不会写单调队列!

弱者就是弱。啥思路没有,码力还不行。

显然的思路是考虑每一个中心点,求最大边长。发现每个点的限制挺难维护的。其实可以把限制拆开,拆成上下左右四个方向,最后求个min。

以向上的方向为例。我们想求向上能扩展的最大边长。从上往下扫,维护半径单调递增的单调队列,每次从队头弹掉半径过小的点,记录最后一个弹掉的队头。然后边长就很好求了。

然后我就写不出来了

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
#define forg(i,x) for(int i=fir[x];i;i=nxt[i])
#define uu unsigned
#define scanf a1234=scanf
#define fre(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout)
#define rint register int
int a1234;
char buf[1<<18],*bufs=buf,*buft=buf;
inline int gc(){
    return bufs==buft&&(buft=(bufs=buf)+fread(buf,1,1<<18,stdin)),bufs==buft?-1:*bufs++;
}
inline void xxx(){for(;;);}
inline int rd(int l,int r){return rand()%(r-l+1)+l;}

const int mxn=2005;
int a[mxn][mxn],ra[mxn],r1[mxn][mxn],r2[mxn][mxn];
int n,m;
inline void man1(rint x){
//横向
    rint md=-1,rp=-1;
    for(rint i=1;i<=m;++i){
        if(i<rp)ra[i]=min(rp-i,ra[md*2-i]);
        else ra[i]=0;
        while(a[x][i+ra[i]]==a[x][i-ra[i]])++ra[i];
        --ra[i];
        if(i+ra[i]>rp)rp=i+ra[i],md=i;
    }
}
inline void man2(rint y){
//纵向
    rint md=-1,rp=-1;
    for(rint i=1;i<=n;++i){
        if(i<rp)ra[i]=min(rp-i,ra[md*2-i]);
        else ra[i]=0;
        while(a[i+ra[i]][y]==a[i-ra[i]][y])++ra[i];
        --ra[i];
        if(i+ra[i]>rp)rp=i+ra[i],md=i;
    }
}
int lm[mxn][mxn];
int q[mxn];
inline void work1(){
    rint qh,qt,lim;
    for(rint j=2;j<m;++j){
        qh=1,qt=0;lim=0;
        for(rint i=1;i<=n;++i){
            while(qh<=qt&&r1[i][j]<=r1[q[qt]][j])--qt;
            q[++qt]=i;
            while(qh<=qt&&r1[q[qh]][j]<i-q[qh]+1)lim=q[qh],++qh;
            if(qh<=qt)lm[i][j]=min(i-lim-1,r1[q[qh]][j]);
        }
    }
}
inline void work2(){
    rint qh,qt,lim;
    for(rint j=2;j<m;++j){
        qh=1,qt=0;lim=n+1;
        for(rint i=n;i;--i){
            while(qh<=qt&&r1[i][j]<=r1[q[qt]][j])--qt;
            q[++qt]=i;
            while(qh<=qt&&r1[q[qh]][j]<q[qh]-i+1)lim=q[qh],++qh;
            if(qh<=qt)lm[i][j]=min(lm[i][j],min(lim-i-1,r1[q[qh]][j]));
        }
    }
}
inline void work3(){
    rint qh,qt,lim;
    for(rint i=2;i<n;++i){
        qh=1,qt=0; lim=0;
        for(rint j=1;j<=m;++j){
            while(qh<=qt&&r2[i][j]<=r2[i][q[qt]])--qt;
            q[++qt]=j;
            while(qh<=qt&&r2[i][q[qh]]<j-q[qh]+1)lim=q[qh],++qh;
            if(qh<=qt)lm[i][j]=min(lm[i][j],min(j-lim-1,r2[i][q[qh]]));
        }
    }
}
inline void work4(){
    rint qh,qt,lim;
    for(rint i=2;i<n;++i){
        qh=1,qt=0; lim=m+1;
        for(rint j=m;j;--j){
            while(qh<=qt&&r2[i][j]<=r2[i][q[qt]])--qt;
            q[++qt]=j;
            
            while(qh<=qt&&r2[i][q[qh]]<q[qh]-j+1)lim=q[qh],++qh;
            if(qh<=qt)lm[i][j]=min(lm[i][j],min(lim-j-1,r2[i][q[qh]]));
        }
    }
}


int main(){
    scanf("%d%d",&n,&m);
    n=n*2+1,m=m*2+1;
    for(int i=2;i<=n;i+=2)for(int j=2;j<=m;j+=2)scanf("%d",&a[i][j]),assert(a[i][j]);
    for(int i=0;i<=n;++i)a[i][0]=-1;for(int j=0;j<=m;++j)a[0][j]=-1;
    for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)if(!a[i][j])a[i][j]=-2;
    
    for(int i=1;i<=n;++i){
        man1(i);
        for(int j=1;j<=m;++j)r1[i][j]=ra[j];
    }
    for(int j=1;j<=m;++j){
        man2(j);
        for(int i=1;i<=n;++i)r2[i][j]=ra[i];
    }
    work1(),work2(),work3(),work4();
//    for(int i=1;i<=n;++i,puts(""))for(int j=1;j<=m;++j)printf("%d ",lim1[i][j]);puts("");
    rint ans=0;
    for(int i=2;i<=n;i+=2)for(int j=2;j<=m;j+=2)ans+=(lm[i][j]+1)>>1;
    for(int i=1;i<=n;i+=2)for(int j=1;j<=m;j+=2)ans+=lm[i][j]>>1;
    printf("%d\n",ans);
    return 0;
}

说几个比较重要的细节吧。

还是以向上的方向为例(work1),如果中心行是\(i\)行,上界是第\(x\)行 (上界一定是关键行,但是第i行可以是辅助行),边长就是\(i-x+1\) 分类讨论一下挺好推的

还有每次更新lim的时候其实应该特判关键行,但是不判也没事。

posted @ 2021-01-15 21:04  yugyppah656  阅读(80)  评论(0编辑  收藏  举报