「CodeNote」 马拉车算法

Posted by Dawn-K's Blog on August 6, 2019

马拉车算法

参考资料

[TOC]

简介

马拉车算法是一种在O(n)时间内求一个字符串的最长回文子串的算法

思路

对于最长回文子串,我们可以有很多朴素算法

  1. 比如穷举所有子串,然后验证这些子串是否是回文的,这样的复杂度是O(n^3),
  2. 比如我们遍历数组,对于每一个元素,我们都认为其是某个回文子串的中心,我们同时向两边伸展,然后取其中的最大值,这样的算法的复杂度是O(n^2)

马拉车算法就是在方法2的基础上进行了扩展,注意方法2做了大量重复的计算,比如,整个数组就是一个回文串,其包含两个对称的小的回文串,这样就做了很多无用功.马拉车算法就是充分利用了这种性质来减少时间复杂度的.

变量

我们设置以下变量


```mx```是目前已知的最长回文子串的最右一位**的后一位**

```Len[]```: **Len[i]表示以i为中心的最长子串能扩展的最长位置-i+1**,即i+Len[i]得到的位置是以i为中心的最长回文子串扩展的最远位置的下一位(此处的设计是为了呼应上文的mx) // Len[]数组的大小应该为原字符串的大小的2倍还多,且Len[0]一定为0

### 预处理

另外我们还要对字符串进行预处理

因为我们发现回文有两种情况,一种是奇数的,一种是偶数的,偶数的形式是没有中心位置的,这就对后续的计算带来极大的不方便,因此我们进行预处理.

预处理的思路是让所有的回文串都变成奇数形式.我们采用如下的思路

```原字符串: ababc```

```预处理后:$#a#b#a#b#c#```

**注意文中的$和#应该都为在字符串本体中不会出现的字符**

这样处理过后,除去开头的$之外,字符串主体一定是奇数

### 算法

我们逐步讨论一下这种情况

![1](https://github.com/Dawn-K/PictureBed/raw/master/2019/04/07/16:13:37.png)



![2](https://github.com/Dawn-K/PictureBed/raw/master/2019/04/07/16:15:41.png)

对于给定的```i```我们找一个和它关于```id```对称的```j```,也就是 ```id-j == i-id```,换言之就是```j==2*id-i```如果发生这种情况,我们就不难发现,```i```和```j``` 的最长回文子串**在id的回文串范围内的部分应该是一模一样的**,但是在外面的部分就无法保证了,当然,最好的情况是```i```和```j```的回文子串范围都很小,这样就保证了**他俩的回文子串一定一模一样**,对于超出的部分我们也没有办法,只能手动扩展,也就是上文的方法2.

如果i大于mx,说明我们无法用已知的最长回文子串来优化这个计算,所以只能老老实实用上文的方法2来计算

经过以上的处理我们不要忘记更新``id``和```mx```,同时记录最长的回文子串的长度```maxn```(代码中写的是sum),最后输出答案即可.

但是! 我们之前进行了预处理,而且Len[i]仅仅是一半的数组长度,这个该怎么进行转换才能得到最长回文子串的答案呢?

其实如果不考虑预处理的话,仅仅就预处理后的字符串而言,回文子串长度是 ```1+2*(Len[i]-1)```,也就是 ```2*Len[i]-1```,预处理后的字符串长度(不考虑开头的$),是```2*s.size()+1``` ,也就是说两个式子联立,就得到了```2*Len[i]-1 == 2*s.size()+1```解得 ```s.size()==Len[i]-1```

所以最后的答案是```max(Len[i]-1)```

## 代码

```c
// hihocoder 1032
#include<iostream>
#include<cstring>
#include<string>
using namespace std;
string s;
string ss;
int lss;
int Len[2000100]; // 一定是要原字符串的二倍多,开小了会RE
int mx;
int id;

void work() { // 马拉车算法模板(无任何更改)
    Len[0] = 0;
    lss = ss.size();
    int sum = 0;
    for (int i = 1; i < lss; ++i) { // 自1开始,不用理会开头的$
        if (i < mx) {
            Len[i] = min(mx - i, Len[2 * id - i]);
        } else {
            Len[i] = 1;
        }
        while (ss[i - Len[i]] == ss[i + Len[i]]) {
            ++Len[i];
        }
        if (i + Len[i] > mx) {
            mx = i + Len[i];
            id = i;
            sum = max(sum, Len[i]);
        }
    }
    cout << sum - 1 << endl;
}

int main() {
    int T;
    cin >> T;
    while (T--) {
        mx = 0;
        id = 0;
        cin >> s;
        int len = s.size();
        ss = "$#";
        for (int i = 0; i < len; ++i) {
            ss += s[i];
            ss += "#";
        }
        work();
    }
    return 0;
}