Chgtaxihe's blog Chgtaxihe's blog
首页
练习
算法学习
读书笔记
小玩意

Chgtaxihe

首页
练习
算法学习
读书笔记
小玩意
  • 数论

  • 动态规划

  • 暴力

  • 图论

  • 数据结构

  • 字符串

    • AC自动机学习笔记
    • KMP/扩展KMP学习笔记
    • Lyndon分解学习笔记
    • Manacher学习笔记
      • 算法步骤
        • 预处理
        • 计算回文子串
        • 模板(洛谷P3805)
    • 后缀数组
    • 后缀自动机学习笔记
    • 回文树
    • 子序列自动机
    • 字符串基础
  • 几何

  • 博弈论

  • 其他

Manacher学习笔记

# Manacher算法

manacher算法是一个可以在O(n)O(n)O(n)复杂度内求出一个字符串sss所有回文子串的算法。

# 算法步骤

给定一个长度为nnn的字符串strstrstr,求其所有回文子串

# 预处理

向strstrstr中插入n+1n+1n+1个分隔符(所选的分隔符不能在strstrstr中出现过),得到串sss

例如,若str=abcbastr=abcbastr=abcba,分隔符为#\##,则有s=#a#b#c#b#a#s=\#a\#b\#c\#b\#a\#s=#a#b#c#b#a#

这样一来就避免了讨论回文子串的奇偶性

# 计算回文子串

定义数组d[i]d[i]d[i]为串sss中,以iii号字符为中心的回文子串的半径。

例如,s=#a#b#a#s=\#a\#b\#a\#s=#a#b#a#,则d=[1,2,1,4,1,2,1]d=[1,2,1,4,1,2,1]d=[1,2,1,4,1,2,1]

显然以iii为中心的回文子串的长度等于r[i]−1r[i]-1r[i]−1


在计算d[0…(i−1)]d[0\dots (i-1)]d[0…(i−1)]的过程中,我们维护找到的最右(即右边界r′r'r′最大)回文子串(l,r)(l,r)(l,r)。

假设d[0…(i−1)]d[0\dots (i-1)]d[0…(i−1)]都已经计算完毕,现在求r[i]r[i]r[i]。

下面分情况讨论

  • 若i>ri>ri>r,则我们通过朴素算法,一位一位计算

  • 否则,有i≤ri\le ri≤r:

    由于串slsl+1…srs_ls_{l+1}\dots s_rsl​sl+1​…sr​是回文串,令j=l+(r−i)j=l+(r-i)j=l+(r−i),有如下情况

    …sl…sj−d[j]+1…sj…sj+d[j]−1⏟回文子串1…si−d[j]+1…si…si+d[j]−1⏟回文子串2…sr⏞回文子串…\ldots\ \overbrace{ s_l\ \ldots\ \underbrace{ s_{j-d[j]+1}\ \ldots\ s_j\ \ldots\ s_{j+d[j]-1} }_\text{回文子串1}\ \ldots\ \underbrace{ s_{i-d[j]+1}\ \ldots\ s_i\ \ldots\ s_{i+d[j]-1} }_\text{回文子串2}\ \ldots\ s_r }^\text{回文子串}\ \ldots …sl​…回文子串1sj−d[j]+1​…sj​…sj+d[j]−1​​​…回文子串2si−d[j]+1​…si​…si+d[j]−1​​​…sr​​回文子串​…

    其中回文子串1等于回文子串2

    但由于i+d[j]−1>ri+d[j]-1>ri+d[j]−1>r的位置我们尚未扫描,不知道是否能构成回文子串,因此需要抛弃

    因此有d[i]=min⁡(d[l+r−i],r−i+1)d[i]=\min(d[l+r-i],r-i+1)d[i]=min(d[l+r−i],r−i+1)

    接着继续通过朴素算法扫描即可

# 模板(洛谷P3805)

题意:给定字符串strstrstr,求其最长回文子串的长度

1.47s

char str[maxn], s[2 * maxn];
int d[2 * maxn];

void solve(){
    cin >> str;
    int len = strlen(str);

    s[0] = '#';
    for(int i=0; i<len; i++){
        s[(i << 1) + 1] = str[i];
        s[(i << 1) + 2] = '#';
    }
    len = (len << 1) + 1;
    s[len] = 0;

    int ans = 0;
    for(int i=1, mid = 0, r = -1; i<len; i++){
        d[i] = (i <= r) ? min(r - i + 1, d[(mid << 1) - i]) : 1;
        while(d[i] <= i && i + d[i] < len && s[i - d[i]] == s[i + d[i]]) d[i]++;
        if(i + d[i] - 1 > r) {
            r = i + d[i] - 1;
            mid = i;
        }
        ans = max(ans, d[i] - 1);
    }
    cout << ans << '\n';

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

如果在字符串sss前再插入一个其他字符(即非分隔符,亦未在strstrstr中出现过),则可以减少while中的条件判断(会快一点点)

1.25s

void solve(){
    cin >> str;
    int len = strlen(str);

    s[0] = '$', s[1] = '#';
    for(int i=0; i<len; i++){
        s[(i << 1) + 2] = str[i];
        s[(i << 1) + 3] = '#';
    }
    len = (len << 1) + 2;
    s[len] = 0;

    int ans = 0;
    for(int i=1, mid = 0, r = -1; i<len; i++){
        d[i] = (i <= r) ? min(r - i + 1, d[(mid << 1) - i]) : 1;
        while(s[i - d[i]] == s[i + d[i]]) d[i]++;
        if(i + d[i] - 1 > r) {
            r = i + d[i] - 1;
            mid = i;
        }
        ans = max(ans, d[i] - 1);
    }
    cout << ans << '\n';
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 参考链接

https://oi-wiki.org/string/manacher/

https://www.luogu.com.cn/problem/solution/P3805

上次更新: 2021/02/24, 03:37:30
Lyndon分解学习笔记
后缀数组

← Lyndon分解学习笔记 后缀数组→

最近更新
01
深入理解Java虚拟机
03-04
02
DNS工作原理
02-27
03
改用VuePress啦
02-23
更多文章>
Theme by Vdoing | Copyright © 2019-2021
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式