[BZOJ1414][ZJOI2009]对称的正方形(manacher+单调栈+二分)

211 篇文章 0 订阅
45 篇文章 0 订阅

题目描述

传送门

题解

跟这道题gang了半晚上,写出来了一个理论复杂度 O(n2log2n) 的做法,然后各种剪枝各种砍常数,最后竟然A了…
网上题解貌似是二分+hash?好像也有用manacher+单调队列并且时间复杂度科学的方法,不过各种看不懂…

首先我们参考manacher的做法,将一些分隔符插入矩阵,来处理奇数偶数
并且对于每一个点都求出来它横纵的最长回文子串
然后枚举对称中心,二分这个对称中心能得到的最大矩形边长
而如果这样二分的话,需要查询二分到的区间横纵回文子串的最小值来判断
如果这题内存够的话,可以用st表实现 O(1) 查询,这样时间复杂度是 O(n2logn) 就非常科学了
可关键是这题内存不够…
那么我们没法预处理st表,但是同样需要查询一段区间的最小值,怎么办呢?
查询区间最值要考虑用单调队列或单调栈,关键是寻找单调性
刚开始我觉得这道题是毫无单调性的,因为每一个点的最长回文子串根本没有规律
但是我们可以考虑把四个方向拆开,也就是说,每一次只处理某一个方向上的最长回文子串的一半
可以发现如果这样处理的话,满足单调性的主要有两个:
①回文中心单调插入②同一个回文中心答案单调
在插入回文中心的时候,维护一个自底向上单调递增的栈,记录栈中每一个元素的位置
还有一个重要的单调性:栈中元素的位置和权值都是单调的
同样是二分一个回文中心的答案,然后每一次在栈中二分第一个小于等于(或大于等于)它的数,即为最小值
这样上下左右做4遍,4个方向的答案取min即为以这个点为中心的正方形的边长
注意特判一些不合法的情况…注意剪枝…

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
#define N 2005
#define inf 1000000000

int n,m,cn,cm,id,mx,ans;
int a[N][N],s[N][N],p[N][N],q[N][N];
int top,stack[N],est[N][N];

int findll(int loc)
{
    int l=1,r=top,mid,ans;
    while (l<=r)
    {
        mid=(l+r)>>1;
        if (stack[mid]>=loc) ans=mid,r=mid-1;
        else l=mid+1;
    }
    return stack[ans];
}
int findrr(int loc)
{
    int l=1,r=top,mid,ans;
    while (l<=r)
    {
        mid=(l+r)>>1;
        if (stack[mid]<=loc) ans=mid,r=mid-1;
        else l=mid+1;
    }
    return stack[ans];
}
int findlp(int x,int y)
{
    int l=3,r=p[x][y],mid,ans=0,loc,Min;
    while (l<=r)
    {
        mid=(l+r)>>1;
        loc=y-mid+1;Min=findll(loc);
        if (q[x][Min]>=mid) ans=mid,l=mid+1;
        else r=mid-1;
    }
    return ans;
}
int findrp(int x,int y)
{
    int l=3,r=p[x][y],mid,ans=0,loc,Min;
    while (l<=r)
    {
        mid=(l+r)>>1;
        loc=y+mid-1;Min=findrr(loc);
        if (q[x][Min]>=mid) ans=mid,l=mid+1;
        else r=mid-1;
    }
    return ans;
}
int findlq(int x,int y)
{
    int l=3,r=q[x][y],mid,ans=0,loc,Min;
    while (l<=r)
    {
        mid=(l+r)>>1;
        loc=x-mid+1;Min=findll(loc);
        if (p[Min][y]>=mid) ans=mid,l=mid+1;
        else r=mid-1;
    }
    return ans;
}
int findrq(int x,int y)
{
    int l=3,r=q[x][y],mid,ans=0,loc,Min;
    while (l<=r)
    {
        mid=(l+r)>>1;
        loc=x+mid-1;Min=findrr(loc);
        if (p[Min][y]>=mid) ans=mid,l=mid+1;
        else r=mid-1;
    }
    return ans;
}
int main()
{
    scanf("%d%d",&n,&m);ans=n*m;
    for (int i=1;i<=n;++i)
        for (int j=1;j<=m;++j) scanf("%d",&a[i][j]);
    for (int i=0;i<=(m<<1|1);++i) s[0][i]=inf+1;
    for (int i=0;i<=(n<<1|1);++i) s[i][0]=inf+1;
    cn=0;
    for (int i=1;i<=n;++i)
    {
        ++cn;
        for (int j=1;j<=(m<<1|1);++j) s[cn][j]=inf+2;
        ++cn;cm=0;
        for (int j=1;j<=m;++j)
            s[cn][++cm]=inf+2,s[cn][++cm]=a[i][j];
        s[cn][++cm]=inf+2;
    }
    ++cn;
    for (int j=1;j<=(m<<1|1);++j) s[cn][j]=inf+2;
    n=n<<1|1,m=m<<1|1;
    for (int i=1;i<=n;++i)
    {
        id=mx=0;
        for (int j=1;j<=m;++j)
        {
            if (j<mx) p[i][j]=min(p[i][2*id-j],mx-j);
            else p[i][j]=1;
            while (s[i][j-p[i][j]]==s[i][j+p[i][j]])
                ++p[i][j];
            if (j+p[i][j]>mx)
            {
                mx=j+p[i][j];
                id=j;
            }
        }
    }
    for (int i=1;i<=m;++i)
    {
        id=mx=0;
        for (int j=1;j<=n;++j)
        {
            if (j<mx) q[j][i]=min(q[2*id-j][i],mx-j);
            else q[j][i]=1;
            while (s[j-q[j][i]][i]==s[j+q[j][i]][i])
                ++q[j][i];
            if (j+q[j][i]>mx)
            {
                mx=j+q[j][i];
                id=j;
            }
        }
    }
    memset(est,127,sizeof(est));
    for (int i=1;i<=n;++i)
    {
        top=0;
        for (int j=1;j<=m;++j)
        {
            while (top&&q[i][j]<=q[i][stack[top]])
                --top;
            stack[++top]=j;
            if ((i&1)!=0&&(j&1)==0||(i&1)==0&&(j&1)!=0||est[i][j]<=2) continue;
            est[i][j]=min(est[i][j],findlp(i,j));
        }
        top=0;
        for (int j=m;j>=1;--j)
        {
            while (top&&q[i][j]<=q[i][stack[top]])
                --top;
            stack[++top]=j;
            if ((i&1)!=0&&(j&1)==0||(i&1)==0&&(j&1)!=0||est[i][j]<=2) continue;
            est[i][j]=min(est[i][j],findrp(i,j));
        }
    }
    for (int i=1;i<=m;++i)
    {
        top=0;
        for (int j=1;j<=n;++j)
        {
            while (top&&p[j][i]<=p[stack[top]][i])
                --top;
            stack[++top]=j;
            if ((j&1)!=0&&(i&1)==0||(j&1)==0&&(i&1)!=0||est[j][i]<=2) continue;
            est[j][i]=min(est[j][i],findlq(j,i));
        }
        top=0;
        for (int j=n;j>=1;--j)
        {
            while (top&&q[j][i]<=q[stack[top]][i])
                --top;
            stack[++top]=j;
            if ((i&1)!=0&&(j&1)==0||(i&1)==0&&(j&1)!=0||est[j][i]<=2) continue;
            est[j][i]=min(est[j][i],findrq(j,i));
            if (est[j][i]-1>1) ans+=(est[j][i]-1)/2;
        }
    }
    printf("%d\n",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值