BZOJ2084 POI2010 Antisymmetry


POI2010 题解整理

Description

对于一个01字符串,如果将这个字符串0和1取反后,再将整个串反过来和原串一样,就称作“反对称”字符串。比如00001111和010101就是反对称的,1001就不是。

现在给出一个长度为N的01字符串,求它有多少个子串是反对称的。

Input

  • 第一行一个正整数 N (N5105)
  • 第二行一个长度为 N 的01字符串。

Output

  • 一个正整数,表示反对称子串的个数。

Sample Input

8
11001011

Sample Output

7


Solution

很显然,本题重点在于如何优化以前的 O(N2) 标准找回文串算法。

回忆以前的做法,我们是枚举中心点,再 O(N) 地向外延伸,增大半径。如果以“构成回文串”为要求,那么这个半径的增长是满足01单调性的,所以我们就可以采用二分答案找最大的满足回文串的半径。

那么找完半径后,如何快速判断左右两段是对称的?我们可以采用Hash来代替通过枚举判断回文的复杂度。于是此题 O(NlogN) 复杂度得解。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define B 200019
#define M 500005
#define P 1000000009
using namespace std;
int n,Base[M],Hnxt[M],Hback[M];
char str[M];
void init(){
    scanf("%d %s",&n,str+1);
    Base[0]=1;for(int i=1;i<=n;i++)Base[i]=1LL*Base[i-1]*B%P;
    Hnxt[0]=0;for(int i=1;i<=n;i++)Hnxt[i]=(1LL*Hnxt[i-1]*B+str[i])%P;
    Hback[n+1]=0;for(int i=n;i>=1;i--)Hback[i]=(1LL*Hback[i+1]*B+(str[i]^'0'^1^'0'))%P;
}
int Hashnxt(int L,int R){
    int ans=Hnxt[R]-1LL*Hnxt[L-1]*Base[R-L+1]%P;
    if(ans<0)ans+=P;
    return ans;
}
int Hashback(int L,int R){
    int ans=Hback[L]-1LL*Hback[R+1]*Base[R-L+1]%P;
    if(ans<0)ans+=P;
    return ans;
}
int main(){
    init();
    long long ans=0;
    for(int pos=1;pos<n;pos++){//对称轴的位置在[pos,pos+1]内 
        int L=1,R=min(pos,n-pos),res=0;
        while(L<=R){
            int mid=L+R>>1;
            if(Hashnxt(pos-mid+1,pos+mid)==Hashback(pos-mid+1,pos+mid))
                res=mid,L=mid+1;
            else R=mid-1;
        }
        ans+=res;
    }
    cout<<ans<<endl;
}

还有一种解法,就是利用Manacher算法在 O(N) 时间找到所有中心点的最大半径长度。这是非常经典的老算法了,它进一步利用回文串的性质:

  • 在回文串对称点左侧出现的小回文串,必然在右侧也会出现。

那么每次出发时,如果已经有小回文串作为基础,就不需要从r=0再枚举一遍。

还有一个问题——为什么只是简化了枚举,复杂度却达到了 O(N) ?如果小回文串在大回文串当中,Mx不产生滑动;如果小回文串不被大回文串完全包含或者在其之外,Mx始终是线性向右滑动。总计复杂度就是Mx的位移复杂度 O(N) 。(我觉得还没讲清楚,当历史遗留问题好了

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define M 500005
using namespace std;
char str[M],buf[M<<1],tmp[M<<1];
int p[M<<1];
int main(){
    int n;
    scanf("%d",&n);
    scanf("%s",str);
    for(int i=0;i<=n;i++)buf[i<<1]=tmp[i<<1]='#';
    for(int i=0;i<n;i++)buf[i<<1|1]=str[i],tmp[i<<1|1]=str[i]^'0'^1^'0';
    n<<=1;
    int id=0,mx=0;
    long long ans=0;
    for(int i=1;i<n;i++){
        if(mx>i)p[i]=min(p[id*2-i],mx-i);
        while(i-p[i]>=0&&buf[i-p[i]]==tmp[i+p[i]])++p[i];
        if(i+p[i]>mx)mx=i+p[i],id=i;
        if(buf[i]=='#')ans+=p[i]>>1;
    }
    cout<<ans<<endl;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值