KMP
对于两个串的匹配,KMP是一个操作简单却相对绕的算法。他的目的是减少一位位挪地方的时间。做法的精髓在于先自己匹配最长公共前后缀,这样我们就可以知道当用于匹配的B串与被匹配的A串失配后可以直接往后挪 nxt [ i ] 位
#include<bits/stdc++.h>
using namespace std;
char s[1000010],t[1000010];
int ans[1000010],nxt[1000010];
int main()
{
scanf("%s%s",t+1,s+1);
int ls=strlen(s+1);
int lt=strlen(t+1);
int k=0;
nxt[1]=0;
for(int i=2;i<=ls;i++)
{
while(k!=0&&s[i]!=s[k+1]) k=nxt[k];
if(s[i]==s[k+1]) k++;
nxt[i]=k;
}
k=0;
int num=0;
for(int i=1;i<=lt;i++)
{
while(k!=0&&t[i]!=s[k+1]) k=nxt[k];
if(t[i]==s[k+1]) k++;
if(k==ls)
{
num++;
ans[num]=i-ls+1;
}
}
for(int i=1;i<=num;i++) printf("%d
",ans[i]);
for(int i=1;i<=ls;i++) printf("%d ",nxt[i]);
return 0;
}
Trie树(AC自动机)
字典树 (Trie) - OI Wiki (oi-wiki.org)
他在用途上于KMP的不同之处在于字典树(字典图)用于多配一,有点像DFS搜索树,但是重点在于需要标记插入进 trie 的是哪些字符串,每次插入完成时在这个字符串所代表的节点处打上标记即可。
马拉车
他讲的是如何用O(n)的时、空复杂度扫回文子串。第一步对于奇偶串分情况讨论,它采用字符填充的处理方式
int manacher() {
int mx = 0, id;//mx为最右边,id为中心点
int maxx = 0;
for (int i = 1; i < len; i++) {
if (mx > i) Len[i] = min(mx - i, Len[2 * id - i]);//判断当前点超没超过mx
else Len[i] = 1;//超过了就让他等于1,之后再进行查找
while (str[i + Len[i]] == str[i - Len[i]]) Len[i]++;//判断当前点是不是最长回文子串,不断的向右扩展
if (Len[i] + i > mx) {//更新mx
mx = Len[i] + i;
id = i;//更新中间点
maxx = max(maxx, Len[i]);//最长回文字串长度
}
}
return (maxx - 1);
}
//截自[darge.]dalao的blog
算法精华大概是分情况状态转移,他有效删去了暴力算法中每次添加一位然后判断过程中小子串被重复枚举判断的时间。先来确定什么是一定要扫的:回文对称中心(设答案点为C,目前正在搜 i )、已知右边界R、每一个可能的对称轴最后扩到了数组P【c是对称中心,c它当初扩到了R,R是目前扩到的最右的地方,现在咱们想以i为中心,看能扩到哪里】
第一种情况是i>R,只能硬着头皮往下搜。
______i’_____C_____i______R
第二种情况是i<R,此时有两种情况:① i 的串完全处在R的串里。那么由于R本身的对称性考虑将串 i 关于点C对称,可得双倍经验。② i 的串有一部分伸出去了,但是这一次关于C对称后产生的 i’ 就会少一节:伸出去的部分不能根据R的对称性对称了。
讲真。。。一开始我打死都不信这玩意儿是O(n)的,但是现在(一下摘自《傻子都能看懂的马拉车Manacher》)首先,我们的i依次往下遍历,而R(最右边界)从来没有回退过吧?其实当我们的R到了最右边,就可以结束了。再不济i自己也能把R一个一个怼到最右。而看情况二和三,直接确定了p[i],根本不用扩,直接遍历下一个元素去了,每个元素o(1)。
综上,由于i依次向右走,而R也没有回退过,最差也就是i和R都到了最右边,而让它们移动一次的代价都是o(1)的,所以总体o(n)。我们对于情况23,虽然知道了它肯定扩不动,但是我们还是给它一个起码是回文的范围,反正它扩一下就没扩动,不影响时间效率的。而情况四也一样,给它一个起码是回文,不用验证的区域,然后接着扩。