[POI 2012]Prefixuffix(字符串Hash)

21 篇文章 0 订阅
3 篇文章 0 订阅

题目链接

http://main.edu.pl/en/archive/oi/19/pre

题目大意

给出一个长度为 n 的字符串,在串中找出一对长度为L(2Ln)的前缀和后缀,使得这两个前缀和后缀是循环同构的,求 Lmax

思路

最终找到的一对合法前缀后缀一定是像这样的形式,分成两部分(红色段,绿色段),前缀里的红色段和后缀里的红色段相同,前缀里的绿色段和后缀里的绿色段相同。
这里写图片描述
我们可以枚举前缀里绿色段的最后一个字符下标 i ,假如我们能每次在O(1)时间得到红色段的长度就好了,不妨设前缀里 [1,i1] 部分是绿色段, [i,i+F[i]1] 部分是红色段(红色段长度是 F[i] )。这样我们每次枚举完 i 后,用i+F[i]1更新答案。

这里有个很有趣的结论: F[i1]F[i]+2
可以用反证法证明。假设 F[i1]>F[i]+2 ,画出下面的两个序列,分别代表了 F[i] F[i1] 的情况。
这里写图片描述
加入了蓝框里的字符后,红色区域变大了很多,我们把下面的 F[i1] 情况的序列拆成左右两半,拼起来看,则除去了左右蓝框以外的中间的红色部分的长度就应该是 F[i] ,然而这里的 F[i] 比实际的 F[i] 大,矛盾了,证毕。
这里写图片描述
利用上述 F[] 存在单调性的结论,我们可以以 O(n) 时间快速求出所有的 F[]

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 1100000
#define MAXP 1000000007
#define MOD 999911659

using namespace std;

typedef long long int LL;

int hash[MAXN],pow[MAXN],a[MAXN],f[MAXN],n;
char s[MAXN];

int gethash(int L,int R)
{
    return (((LL)hash[R]-(LL)pow[R-L+1]*(LL)hash[L-1]%(LL)MOD)%MOD+MOD)%MOD;
}

int main()
{
    int ans=0;
    scanf("%d",&n);
    pow[0]=1;
    for(int i=1;i<=n;i++)
        pow[i]=(LL)pow[i-1]*(LL)MAXP%(LL)MOD;
    scanf("%s",s+1);
    for(int i=1;i<=n;i++) a[i]=s[i];
    for(int i=1;i<=n;i++)
        hash[i]=((LL)hash[i-1]*(LL)MAXP+(LL)a[i])%(LL)MOD;
    for(int i=n/2;i>=1;i--)
    {
        f[i]=min(f[i+1]+2,n/2-i+1);
        while(f[i]>0&&gethash(i,i+f[i]-1)!=gethash(n-i+1-f[i]+1,n-i+1)) f[i]--;
    }
    for(int i=1;i<=n;i++) a[i]+=a[i-1];
    if(f[1]) ans=max(ans,f[1]);
    for(int i=2;i<=n/2;i++)
        if(gethash(1,i-1)==gethash(n-i+2,n)&&a[i-1]-a[0]==a[n]-a[n-i+1])
            ans=max(ans,i+f[i]-1);
    printf("%d\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值