#include<iostream> #include<string> usingnamespace std; constint maxn = 5e5+10; constint charsize = 26; int nxt[maxn+1][charsize]; //字典树 int total[maxn+1]; //记录叶子节点数 int cnt = 0;
voidinsert(char s[],int len){ int now = 0; for(int i=0;i<len;i++){ int x = s[i] - 'a'; if(!nxt[now][x]){ nxt[now][x] = ++cnt; } now = nxt[now][x]; total[now]++; //说明有一个单词经过了这个前缀,计数 } }
intsearch(char s[],int len){ int now = 0; for(int i=0;i<len;i++){ int x = s[i] - 'a'; if(!nxt[now][x]){ return0; } now = nxt[now][x]; } return total[now]; }
intmain(){ char str[11]; while(cin.getline(str)){ int len = strlen(str); if(!len) break; insert(str,len); } while(cin,getline(str)){ cout<<search(str,strlen(str))<<endl; } }
简单上来讲,就是找最大的公共前后缀,然后由前缀部分跳转到后缀部分,整个 next 数组就是开一个数组去记录当匹配到了当前字符时,如果出现失配,应该从开头跳转到哪个字符。
求 next 数组,先从左往右遍历这个数组,然后去计算当前位置的最大公共前后缀,然后计算当前位的下一个位的 next 值(即要拿哪一位和下一位进行比较),如下图中,i=5 对应的最大公共前后缀长度为 3(ABA),那么假设下一位发生失配,就要拿前缀 ABA 的下一个字符 B 来匹配。 所以 $next[i+1]=index(B)=3$
思路比较简单,KMP 算法难就难在代码怎么写,求 next 数组可以看成是一个动态规划过程,因为现在的公共前后缀都是前面的公共前后缀加上来的,用下面这张图理解
如果当前的 next 配对上了,next[i+1] 直接加 1 就好,如果没配对上,就要找他上一次的最大前缀,然后再看能不能配对上,循环这个过程直到配对上了或者回到了起点。