Manacher(马拉车)算法
Manacher(马拉车)
简介
一种字符串算法,能够在 \(O(n)\) 复杂度解决如下问题:
给定一个长度为 \(n\) 的字符串 \(s\),找到字符串中最大的回文子串。
使用哈希复杂度是 \(O(n\log n)\)。
实现
考虑处理出一个数组 \(d1_i\) 和 \(d2_i\) 代表以位置 \(i\) 为中心的长度为奇数和长度为偶数的回文串个数。二者也表示了以位置 \(i\) 为中心的最长回文串的半径长度。
例如:
-
\(abcbac\),以第一个 \(c\) 为中心,可以找出回文串
c,bcb,abcba共三个,\(d1_i=3\),所以回文串长度为 \(3\times 2-1=5\)。 -
\(abba\),以第一个 \(b\) 为中心,可以找出回文串
bb,abba共两个,\(d2_i=2\),所以回文串长度为 \(2\times 2=4\)。
偶数和奇数的处理方式是不同的。但如果我们将字符串中任意两个字符间都放一个 # 字符的话,原字符串的偶数长度回文串就变为了 # 字符的奇数长度回文串。
答案为 \(\max d_i-1\),减去的是 # 的贡献。
考虑如何实现。
先维护已经找到的最靠右的子回文串的两个端点 \((l,r)\)。初始时 \(l=0,r=-1\)。
考虑现在要计算一个 \(d_i\),\(d_1\sim d_{i-1}\) 的值都已经被计算。
- \(i>r\) 即当前 \(i\) 处于回文串之外,考虑朴素算法。
连续的增加 \(d_i\),每次判断 \(i-d_i\) 是否和 \(i+d_i\) 一样。注意更新 \((l,r)\)。
- \(i\le r\),即当前 \(i\) 处于回文串之中,考虑利用已经处理的算法计算答案。
考虑 \(i\) 在 \((l,r)\) 的对称位置 \(i'\):

由于 \((l,r)\) 是回文串,所以 \(i'\) 的回文串可以复制到 \(i\) 上,即 \(d_{i'}\to d_i\)。
注意 \(i'\) 的回文串右端点可能会 \(>r\),此时是不一定合法的(因为 \(r\) 之后的字符不一定满足对称性),所以 \(d_i\) 初始值最大为 \(r-i\)。
由于 \(d_i\) 还有可能更大,所以考虑用朴素算法继续拓展。
复杂度分析
每次执行朴素算法都会让 \(r\) 增加,复制 \(i'\) 的贡献不会产生时间复杂度,所以 \(O(n)\)。
Code
scanf("%s",s+1);
t[++n]='!';
t[++n]='#';
int len=strlen(s+1);
F(i,1,len)
{
t[++n]=s[i];
t[++n]='#';
}
t[++n]='#';
int l=0,r=-1,ans=1;
F(i,1,n)
{
if(i<=r)
{
int ii=l+(r-i);
d[i]=MIN(d[ii],r-i+1);
}
else d[i]=1;
while(t[i+d[i]]==t[i-d[i]]) d[i]++;
if(i+d[i]-1>r) r=i+d[i]-1,l=i-d[i]+1;
ans=MAX(ans,d[i]-1);
}
「POI2010」Antisymmetry
分析
发现一个字符串需要满足 \(s_{i+k}\oplus s_{i-k}=1\) 就是不相同,做马拉车的时候判断反过来就行。
容易发现的是奇数长度的一定不满足。
思路
处理 \(d\) 数组,最后答案怎么计算?
比如 #1#0#,\(d_i=3\),贡献了一个子串 10。
比如 #1#1#0#0#,\(d_i=5\),贡献了两个子串 10 和 1100。
注意题目问的是子串所以可以重复,不用去重。所以答案为 \(\sum \frac{d_i}{2}\)。
细节
#和#可以匹配。
具体来讲可以使用一个 map 记录,mp[#]=#,mp[1]=0,mp[0]=1。
- 只处理
t[i]=#的情况。因为答案只和偶长度回文串有关,处理了其他t[i]的话会导致 \((l,r)\) 被乱改。
Code
int l=0,r=-1;
F(i,2,n-1)
{
if(t[i]!='#') continue;
if(i<=r)
{
int ii=l+(r-i);
d[i]=MIN(d[ii],r-i+1);
}
else d[i]=1;
while(t[i+d[i]]==mp[t[i-d[i]]]) d[i]++;
if(i+d[i]-1>r) r=i+d[i]-1,l=i-d[i]+1;
}
int ans=0;
F(i,2,n-1) if(t[i]=='#') ans+=d[i]/2;
put(ans);

浙公网安备 33010602011771号