Luogu P9606 CERC2019 ABB 题解 [ 绿 ] [ KMP ] [ 字符串哈希 ]

ABB:KMP 的做法非常巧妙。

哈希

思路

显然正着做一遍哈希,倒着做一遍哈希,然后枚举回文中心即可。

时间复杂度 O(n)

代码

#include <bits/stdc++.h>
#define fi first
#define se second
#define lc (p<<1)
#define rc ((p<<1)|1)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pi;
const int N=400005;
const ull base=13331;
ull phash[N],shash[N],pw[N];
int n,ans;
char s[N];
void dohash()
{
    pw[0]=1;
    for(int i=1;i<N;i++)pw[i]=pw[i-1]*base;
    for(int i=1;i<=n;i++)phash[i]=phash[i-1]*base+s[i];
    for(int i=n;i>=1;i--)shash[i]=shash[i+1]*base+s[i];
}
ull gethash(int op,int l,int r)
{
    if(op==0)return (phash[r]-phash[l-1]*pw[r-l+1]);
    return (shash[l]-shash[r+1]*pw[r-l+1]);
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>s+1;
    ans=n-1;
    if(n==1)
    {
        cout<<0;
        return 0;
    }
    dohash();
    for(int i=1;i<=n;i++)
    {
        if(i-1>=n-(i+1)+1)
        {
            int len=n-(i+1)+1;
            if(gethash(0,i-1-len+1,i-1)==gethash(1,i+1,i+1+len-1))
            {
                ans=min(ans,i-1-len);
            }
        }
        if(i>=n-(i+1)+1)
        {
            int len=n-(i+1)+1;
            if(gethash(0,i-len+1,i)==gethash(1,i+1,i+1+len-1))
            {
                ans=min(ans,i-len);
            }
        }
    }
    cout<<ans;
    return 0;
}

哈希的代码又臭又长,肯定不是这题的最优解。

KMP

思路

我们考虑这题要求的本质是什么,显然是求字符串 s 最长的回文后缀的长度 l,那么答案就是 nl。因为剩下的那些必须对称过去。

那么如何求最长的回文后缀呢?我们可以先把字符串 s 反转为 s,把后缀转化为前缀便于处理。

由于回文串正着读和反着读都一样,可以发现回文后缀满足是字符串 s 的一段前缀,也是字符串 s 的一段后缀

因此如果字符串 s 的一段前缀与字符串 s 的一段后缀相等,就说明这是一个合法的回文后缀。

如果要让这个后缀最长,那么显然是 KMP 的 nextn,就求出来了。

实现上,我们可以将最后的主串设置为 s 与任意一个分隔符与 s 拼接起来的字符串,然后做一遍 KMP 即可。

时间复杂度 O(n)

代码

非常简短,注意字符串开两倍的长度。

#include <bits/stdc++.h>
using namespace std;
const int N=800005;
int n,ne[N];
char s[N];
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>s+n+2;
    s[n+1]='%';
    for(int i=1,j=2*n+1;i<=n;i++,j--)s[i]=s[j];
    int now=0;
    for(int i=2;i<=2*n+1;i++)
    {
        while(now&&s[now+1]!=s[i])now=ne[now];
        if(s[now+1]==s[i])now++;
        ne[i]=now;
    }
    cout<<n-ne[2*n+1];
    return 0;
}
posted @   KS_Fszha  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示