Manacher

CSP 前填坑,但是这玩意好简单,感觉学了自动机之后这一类串串题都非常容易理解了。


Manacher

用途和 KMP 比较像,不怎么能在上面 dp,但是有可能会有其思想的延伸应用。

天依宝宝可爱!


洛谷 P3805

板子。

下面「回文串 \(i\)」及类似的东西的意思就是「以 \(i\) 为中心的回文串」。

首先为了统一奇数回文和偶数回文,需要把字符串搞成 #a#b#c#d# 的样子。

具体思路就是维护一个 \(p_i\) 表示以 \(i\) 为中点的最长回文串的一半长度。

例如对于串 #a#b#a#\(p_i = 4\)

然后再维护一个 \(j\) 和一个 \(r\),表示在已经做完的位置(即 \(1 \sim i-1\))中回文串能延伸到的最靠右位置为 \(r\),能延伸到 \(r\) 的这个串的中点为 \(j\),注意我们并不关心有多个 \(j\) 的情况,随便一个即可。

例如对于串 #x#c#b#c#a#c#b#c#y#,假设当前做到了最后面那个 b,此时前面那个 a 能延伸到的最大位置 \(r=17\) 也就是 y 前面那个 # 的位置,\(j=10\) 也就是 a 的位置。

那么在算 \(p_i\) 的时候,就可以根据 \(j,r\) 来搞事情了。

  1. \(i<r\) 时,\(i\) 是可以完全被 \(j\) 这个回文串覆盖的,于是令 \(i’\)\(i\) 关于 \(j\) 的对称点,那么在回文串 \(j\) 的范围内\(i’\) 回文的部分对应到 \(i\) 上也一定回文,即 \(p_i \ge \min(p_{i'} , r-i)\),根据回文串的性质显然。于是先令 \(p_i \gets \min(p_{i'} , r-i)\)

    但是这还不够,因为回文串 \(i\) 不一定只有这么短,还有可能延伸到回文串 \(j\) 的外面,所以还需要往外扩展,可以发现此时就没有什么能利用的东西了,所以直接暴力扩展即可。

  2. \(i > r\) 时,直接暴力扩展就可以。

  3. 哦你说 \(i = r\),随便丢到哪个情况里都是一样的。

复杂度是 \(O(n)\) 的,因为只要出现了暴力扩展,那么一定是因为需要扩展的范围超过了 \(r\) 导致的,所以就一定会更新 \(r\)。那么显然 \(O(n)\)

最后放个代码:

{s="$#"; char c; while(read(c),!fast_io::isEOF) s+=c,s+='#';}
n=s.size()-1;

int r=0,j=0,ans=0;
rep(i,1,n)
{
    p[i]=r>i?min(p[(j<<1)-i],(signed)(r-i)):1; // 冒号前是情况 1,冒号后是情况 2
    while(s[i-p[i]]==s[i+p[i]]) ++p[i]; // 暴力往外扩展
    i+p[i]>=r && (r=i+p[i],j=i); // 更新 j,r
    chmax(ans,p[i]-1); // 更新答案
}

write(ans);

submission

天依宝宝可爱!


洛谷 P4555

思维难度:\(\color{#F39C11} 橙\) *1100

显然是先跑一遍 Manacher,然后求一个 \(l_i\) 表示以 \(i\) 为左端点的回文串的最右端点,\(r_i\) 同理,然后取 \(\max _{i} \{ l_i - r_i + 1 \}\) 就是答案。

这个直接对每个 \(i \pm p_i\) chmax/min 一遍不就好了?但是发现不过样例,因为这样只考虑了最长回文串,并没有考虑较短的回文串会和另一个更长的回文串组合的情况。

于是要 \(\forall j \le p_i\),都更新一遍 \(i \pm j\),发现可以上线段树,于是做完了。

但是作为一名数据结构不爱好者,我们发现这玩意可以直接做一遍前缀 max(后缀 min)来求,于是便在 \(O(n)\) 的复杂度内做完了。

注意考虑回文串不能是空串的情况,也就是 # 不能单独作为一个回文串,也就是必须满足 \(l_i > i \land r_i < i\)

submission

天依宝宝可爱!


CF1326D2

思维难度:\(\color{#FE4C61} 红\) *900

把前后缀能构成回文的扣出来,剩下的部分选一个最长的回文前缀或后缀即可。

submission

天依宝宝可爱!

posted @ 2025-10-29 11:28  little__bug  阅读(30)  评论(0)    收藏  举报