Newcoder 58 C.最长回文(Manacher+二分+hash)

本文探讨了如何从两个等长字符串中选取子串并拼接形成最长回文串的问题,利用Manacher算法和字符串哈希技巧进行高效求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

有两个长度均为nnn的字符串AAABBB。可以从AAA中选一个可以为空的子串A[l1..r1]A[l_1..r_1]A[l1..r1]BBB中选一个可以为空的子串B[l2..r2]B[l_2..r_2]B[l2..r2],满足r1=l2r_1=l_2r1=l2,然后把它们拼起来(A[l1..r1]+B[l2..r2])(A[l_1..r_1]+B[l_2..r_2])A[l1..r1]+B[l2..r2]。求用这样的方法能得到的最长回文串的长度。注意:求的不是本质不同的回文串个数哦!!!

Input

第一行一个数nnn

第二行表示字符串AAA

第三行表示字符串BBB

(1≤n≤105)(1\le n\le 10^5)(1n105)

Output

输出一行一个数表示答案

Sample Input

5
ZQZFC
NSZXL

Sample Output

3

Solution

一个显然的结论是,所选子串必然至少有一个是原串中的极大回文串(或者说答案必然可以写成这样的形式),不妨认为是AAA串的一个极大回文串拼上AAA串左端一个子串和BBB串一个子串构成,那么后两部分需要回文,将BBB串翻转后这两部分需要相同,首先对AAA串跑一遍ManacherManacherManacher,然后二分两端拼接的长度,把AAABBB的反串hashhashhash以快速判断两个子串是否相同

Code

#include<cstdio>
#include<algorithm>
using namespace std;
typedef unsigned long long ull;
const int maxn=200005;
char A[maxn],B[maxn],s[maxn];
int len[maxn];
void Manacher(char *s,int n)
{
    len[0]=1;
    int mx=0,id=0;
    s[n]='#';
    for(int i=1;i<n;i++)
    {
        len[i]=mx>i?min(len[2*id-i],mx-i):1;
        while(i-len[i]>=0&&s[i+len[i]]==s[i-len[i]])len[i]++;
        if(i+len[i]>mx)
        {
            mx=i+len[i];
            id=i;
        }
    }
    for(int i=0;i<n;i++)len[i]=2*len[i]-1;
}
ull base=17,b[maxn];
struct Hash
{
    ull h[maxn];
    void init(char *s,int n)
    {
        h[0]=s[0];
        for(int i=1;i<n;i++)h[i]=h[i-1]*base+s[i];
    }
    ull query(int l,int r)
    {
        if(l==0)return h[r];
        return h[r]-h[l-1]*b[r-l+1];
    }
}ha,hb;
int n;
int Solve(char *A,char *B)
{
    Manacher(A,n);
    reverse(B,B+n);
    ha.init(A,n),hb.init(B,n);
    int ans=0;
    for(int i=0;i<n;i++)
    {
        int l1=i-len[i]/2,r1=i+len[i]/2;
        int l2=n+1-r1;
        int l=0,r=min(l1,l2),mid,temp=0;
        while(l<=r)
        {
            mid=(l+r)/2;
            if(ha.query(l1-mid,l1-1)==hb.query(l2-mid,l2-1))l=mid+1,temp=mid;
            else r=mid-1;
        }
        temp=2*temp+len[i];
        if(i&1)temp=(temp+1)/4*2;
        else temp=temp/4*2+1;
        ans=max(ans,temp);
    }
    return ans;
}
int main()
{
    scanf("%d",&n);
    scanf("%s",s);
    for(int i=n-1;i>=0;i--)A[2*i]=s[i];
    scanf("%s",s);
    for(int i=n-1;i>=0;i--)B[2*i]=s[i];
    n=2*n-1;
    b[0]=1;
    for(int i=1;i<=n;i++)b[i]=b[i-1]*base;
    int ans=Solve(A,B);
    reverse(A,A+n);
    ans=max(ans,Solve(B,A));
    printf("%d\n",ans);
    return 0;
}
### Python 实现最长回文子串算法 #### 方法一:暴力搜索法 暴力搜索法通过遍历所有可能的子串并检查其是否为回文来解决问题。这种方法简单直观,但时间复杂度较高。 ```python def longestPalindrome_brute_force(s): def is_palindrome(substring): return substring == substring[::-1] n = len(s) max_len = 0 start = 0 for i in range(n): for j in range(i, n): if is_palindrome(s[i:j+1]) and (j - i + 1) > max_len: max_len = j - i + 1 start = i return s[start:start + max_len] ``` 此方法的时间复杂度为 O()[^1]。 #### 方法二:动态规划法 动态规划法利用了回文的特点——如果一个字符串回文,则去掉两端字符后的部分仍然是回文。这使得可以通过构建二维表的方式优化查找过程。 ```python def longestPalindrome_dp(s): n = len(s) dp = [[False] * n for _ in range(n)] start = 0 maxLength = 1 for i in range(n): dp[i][i] = True for cl in range(2, n + 1): for i in range(n - cl + 1): j = i + cl - 1 if s[i] == s[j] and cl == 2: dp[i][j] = True elif s[i] == s[j]: dp[i][j] = dp[i + 1][j - 1] if dp[i][j] and cl > maxLength: start = i maxLength = cl return s[start:start + maxLength] ``` 该方法的时间复杂度为 O()[^2]。 #### 方法三:中心扩展法 中心扩展法则基于这样一个事实:任何长度大于等于3的回文都至少有一个中心位置。可以从每个潜在的中心向两侧扩散直到遇到不匹配为止。 ```python def expand_around_center(s, left, right): L, R = left, right while L >= 0 and R < len(s) and s[L] == s[R]: L -= 1 R += 1 return R - L - 1 def longestPalindrome_expand(s): if not s or len(s) < 1: return "" start = end = 0 for i in range(len(s)): len1 = expand_around_center(s, i, i) len2 = expand_around_center(s, i, i + 1) maxLen = max(len1, len2) if maxLen > end - start: start = i - (maxLen - 1) // 2 end = i + maxLen // 2 return s[start:end + 1] ``` 此方法同样具有较好的性能表现,在实际应用中往往能取得接近线性级别的效率[^4]。 #### 方法四:Manacher算法 Manacher算法是一种专门针对这个问题设计的有效解决方案,能够在O(n)时间内完成计算。它通过对原始字符串预处理以及巧妙地维护辅助数组实现了这一目标。 ```python def preprocess_string(s): if not s: return "^$" result = ["^"] for char in s: result.append("#") result.append(char) result.append("#$") return ''.join(result) def manachers_algorithm(s): T = preprocess_string(s) P = [0] * len(T) C = R = 0 for i in range(1, len(T)-1): mirr = 2*C - i if R > i: P[i] = min(R-i, P[mirr]) while T[i+(P[i]+1)] == T[i-(P[i]+1)]: P[i] += 1 if i + P[i] > R: C = i R = i + P[i] maxLen = 0 centerIndex = 0 for i in range(1, len(P)-1): if P[i] > maxLen: maxLen = P[i] centerIndex = i startIndex = (centerIndex - maxLen) // 2 return s[startIndex:startIndex + maxLen] ``` 上述四种方法各有优劣之处,具体选择取决于应用场景个人偏好。其中Manacher算法由于其优秀的时特性而被广泛推荐用于解决此类问题[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值