KMP算法有两种计算失配函数的方法
未优化过的失配函数 , f[i] 就表示 str[0~i-1] 的公共最长前后缀( 不包括str[0~i-1]本身,比如aaa的最长公共前后缀长度为2 ) , 这个特性必须牢记
从下面的例子应该也可以看出
0 1 2 3 4 5 6 7 8 9 10
str a a a a b b a a b a ‘\0’
f 0 0 1 2 3 0 0 1 2 0 1
优化过后的失配函数,虽然匹配的性能要好点 , 不过失去上述这么优美的特性 , 基本也就是废了( 应该不至于出道裸的匹配,然后卡失配函数吧
)
=========================================我是分割线=================================================
模板和原理我就不赘述了,网上资料很多,贴个模板完事
未优化的失配函数
void get_nex( char * str ){
int len = strlen( str ) ;
f[0] = 0 , f[1] = 0 ;
for( int i = 1 ; i < len ; i ++ ) {
int j = f[i] ;
while( j && str[i] != str[j] ) j = f[j] ;
// 跟某个前缀匹配成功,那么下次从下个前缀可以匹配
if( str[i] == str[j] ) f[i+1] = j + 1 ;
// 否则从头开始匹配
else f[i+1] = 0 ;
}
}
KMP 匹配函数
int KMP( char * str , char * sub_str ) {
int len1 = strlen( str ) , len2 = strlen( sub_str ) ;
// 当前跟子串的那个字符匹配
int j = 0 ;
int ans = 0 ;
for( int i = 0 ; i < len1 ; i ++ ) {
// 如果成功下次匹配下一个字符 , 否则一直沿失配边走
while( j && str[i] != sub_str[j] ) j = f[j] ;
if( str[i] == sub_str[j] ) j ++ ;
if( j == len2 ) j = f[j] , ans ++ ;
}
return ans ;
}=====================================我是华丽丽的分割线==============================================
比较常见的问题 ( 应该要熟练掌握 ) :
1. KMP 求出 S[0...i-1] 的所有的公共前后缀的问题
这个东西在某些题目中会用于统计
很显然 , f[i] 的值就是 S[0...i-1] 的最长公共前后缀 ( 还是一样不包括串本身的 ),因为那个华丽丽的特性
然后怎么求稍微短一点的 ? 仔细一想,s[0...i-1] 次长( 存在的话 ) 的公共前后缀 ,就是 s[0...i-1] 和 s[i-f[i],i-1] 的最长公共前后缀。
那么又因为s[i-f[i],i-1] 本身就是原串是 s[0,f[i]-1] , 所以次长的公共前后缀就是 f[f[i]] 了
后面的就以此类推就行
2. KMP 求循环串
什么是 循环串 就是某个字符串可以由它的前缀重复几次得到这个串本身 。比如 aabaabaabaab ,可以有 aab , aabaab , aabaabaabaab 循环得出
假设我们称 aab , aabaab 这样的为循环节
那么怎么求最长的循环节呢 ? len - f[len] 就是循环节的长度 ,当然要同时满足 len % ( len - f[len] ) == 0
假设字符串的长度为 len ,len - f[len] = K , K * t = len
那么根据上述结论 , 整个字符串就应该是由 t 个 s[0...K-1] 循环组成的 ,如何得到这个呢 ?
S[0...K-1] , S[K,2*K-1] ...................... S[(t-1)*K,t*K-1 ]
S[0,K-1] , S[K,2*K-1] ..... S[(t-2)*K,(t-1)*K-1] , S[(t-1)*K,t*K-1]
因为我们知道 f[len] = len - K , 也就是说 字符串的前 ( t-1) * K 个字符串和 后( t - 1 ) * K 个字符串的相等的
那么我们就可以得到 S[0,K-1] == S[K,2*K-1] .... S[(t-1)*K,t*K-1] = S[(t-2)*K,(t-1)*K-1]
所以我们很容易得到 S[0,K-1] == S[K,2*K-1] = ... = S[(t-1)*K,t*K-1]
OK,我们已经得到答案了
至于再大一点的循环节,我们只要求小一点的最长公共前后缀就行了。
=====================================又是华丽丽的分割线==============================================
KMP题目 :
POJ 3461 Oulipo
POJ 1019 Number Sequence
两道模板题 ...
POJ 2406 Power Strings
求最短的循环节的循环次数
上面已经说过了,直接 len / (len - f[len]) 用这个公式就行
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
#define MAXN 1000005
char str[MAXN] ;
int f[MAXN] ;
void get_nex( char * str ) {
int len = strlen( str ) ;
int j;
f[0] = f[1] = 0 ;
for( int i = 1 ; i < len ; i ++ ) {
j = f[i] ;
while( j && str[i] != str[j] ) j = f[j] ;
if( str[i] == str[j] ) f[i+1] = j + 1 ;
else f[i+1] = 0 ;
}
}
int main(){
while( scanf( "%s" , str ) != EOF ) {
if( strcmp( "." , str ) == 0 ) break;
get_nex( str ) ;
int len = strlen( str ) ;
if( len % ( len - f[len] ) == 0 ) {
printf( "%d\n" , len / ( len - f[len] ) ) ;
}else{
printf( "1\n" ) ;
}
}
return 0 ;
}求各个前缀在字符串中出现的次数的总和
dp[i] 表示 以字符i-1为结尾的和前缀相同的子串的有多少
那么 : dp[i] = dp[f[i]] + 1 , 首先以为结尾的字符串本身肯定是一个前缀,所以至少是1
然后dp[f[i]] 是以 f[i] -1 为结尾的前缀数 , 而s[0~f[i]-1]又刚好就是s[0~i-1]的后缀,所以第i-1个字符结尾的长度小于i的字符串总数为dp[f[i]]
最后求个和就行了
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
#define mod 10007
char str[200005] ;
int f[200005] ;
int dp[200005] ;
void get_nex( char * str , int len ){
f[0] = f[1] = 0 ;
int j ;
for( int i = 1 ; i < len ; i ++ ) {
j = f[i] ;
while( j && str[i] != str[j] ) j = f[j] ;
if( str[i] == str[j] ) f[i+1] = j + 1 ;
else f[i+1] = 0 ;
}
}
int main(){
int cas ;
scanf( "%d" , &cas ) ;
while( cas -- ) {
int len ;
scanf( "%d" , &len ) ;
scanf( "%s" , str ) ;
get_nex( str , len );
dp[1] = 1 ;
int sum = 1 ;
for( int i = 2 ; i <= len ; i ++ ) {
dp[i] = dp[f[i]] + 1 ;
sum += dp[i] ;
sum %= mod ;
}
printf( "%d\n" , sum ) ;
}
return 0 ;
}求既是字符串的前缀,又是字符串的后缀,又在中间出现的最长子串( 三个不能重叠 ) 。
首先我们要求出所有既是前缀又是后缀的字符串的长度,用vis数组标记下
然后枚举中间的字符串的终点,求出 f[i] 在判断下合不合法 ,有没有标记过
最后求出长度最大,既合法,又被vis数组标记过的长度就行了
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
#define MAXN 1000005
char str[MAXN] ;
int f[MAXN] ;
bool vis[MAXN] ;
void get_nex( char * str , int len ) {
f[0] = f[1] = 0 ;
int j ;
for( int i = 1 ; i < len ; i ++ ) {
j = f[i] ;
while( j && str[i] != str[j] ) j = f[j] ;
if( str[i] == str[j] ) f[i+1] = j + 1 ;
else f[i+1] = j ;
}
}
int main(){
int cas ;
scanf( "%d" , &cas ) ;
while( cas -- ) {
scanf( "%s" , str ) ;
int len = strlen( str ) ;
memset( vis , false , sizeof(vis) ) ;
get_nex( str , len ) ;
int Len = f[len] ;
while( Len ) {
vis[Len] = true ;
Len = f[Len] ;
}
int ans = 0 ;
for( int i = 2 ; i < len ; i ++ ) {
int ll = f[i] ;
int tmp = min( min( ll , i / 2 ) , min( f[len] , len - i ) ) ;
if( vis[tmp] ) ans = max( ans , tmp ) ;
}
printf( "%d\n" , ans ) ;
}
return 0 ;
}就是求二维情况的循环串
我是先把一维用多项式hash压缩掉,求一维的循环节 , 然后在压缩另外一维 , 再求一遍就行了
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std ;
#define MAXN 10005
char mp[MAXN][85] ;
typedef unsigned __int64 uLL ;
uLL a[MAXN] ;
int f[MAXN] ;
void get_nex( uLL * a , int len ){
f[0] = 0 , f[1] = 0 ;
int j ;
for( int i = 1 ; i < len ; i ++ ) {
j = f[i] ;
while( j && a[i] != a[j] ) j = f[j] ;
if( a[i] == a[j] ) f[i+1] = j + 1 ;
else f[i+1] = 0 ;
}
}
int main(){
int n , m ;
while( scanf( "%d%d" , &n, &m ) != EOF ) {
for( int i = 0 ; i < n ; i ++ ) {
scanf( "%s" , mp[i] ) ;
a[i] = 0 ;
for( int j = 0 ; j < m ; j ++ ) {
a[i] = a[i] * 10007 + mp[i][j] - 'A' ;
}
}
get_nex( a , n ) ;
int H = n - f[n] ;
for( int i = 0 ; i < m ; i ++ ) {
a[i] = 0 ;
for( int j = 0 ; j < H ; j ++ ) {
a[i] = a[i] * 10007 + mp[j][i] - 'A' ;
}
}
get_nex( a , m ) ;
int W = m - f[m] ;
printf( "%d\n" , H * W ) ;
}
return 0 ;
}给你两个串, 让你求第一串和第二个串的最长公共前后缀
我直接把两个字符串用#连起来,求下失配函数, f[len]就是要求的答案了
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
#define MAXN 50005 * 2
char str[MAXN] ;
int f[MAXN] ;
void get_nex( char * str , int len ){
f[0] = f[1] = 0 ;
int j ;
for( int i = 1 ; i < len ; i ++ ) {
j = f[i] ;
while( j && str[i] != str[j] ) j = f[j] ;
if( str[i] == str[j] ) f[i+1] = j + 1 ;
else f[i+1] = 0 ;
}
}
int main(){
while( scanf( "%s" , str ) != EOF ) {
int len = strlen( str ) ;
str[len] = '#' ;
scanf( "%s" , str + len + 1 ) ;
len = strlen( str ) ;
get_nex( str , len ) ;
if( f[len] ) {
str[f[len]] = 0 ;
printf( "%s %d\n" , str , f[len] ) ;
}else{
printf( "0\n" ) ;
}
}
return 0 ;
}HDOJ 3746 Cyclic Nacklace
问至少增加一个字符可以变成至少有两个以上循环节的循环串
如果本身就是循环串 ,如果只有一个循环节,那要在复制一次字符串 , 否则答案就是0
不是的话,就看 len - f[len] 的长度 ,看添加几个能补到被len整数就行了
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <stack>
using namespace std;
#define MAXN 400005
char str[MAXN] ;
int f[MAXN] ;
void get_nex( char * str , int len ){
f[1] = f[0] = 0 ;
int j;
for( int i = 1 ; i < len ; i ++ ) {
j = f[i] ;
while( j && str[i] != str[j] ) j = f[j] ;
if( str[i] == str[j] ) f[i+1] = j + 1 ;
else f[i+1] = 0 ;
}
}
int main(){
while( scanf( "%s" , str ) != EOF ) {
int len = strlen( str ) ;
get_nex( str , len ) ;
stack<int> s ;
s.push( len ) ;
while( f[len] ) {
s.push( f[len] ) ;
len = f[len] ;
}
bool first = true ;
while( !s.empty() ) {
printf( first?"%d" :" %d" , s.top() ) ;
s.pop() ;
first = false ;
}
puts( "" ) ;
}
return 0 ;
}就是求所有的前后缀的长度,从小到大输出来
上面已经讲过了 , 直接 len , f[len] , f[f[len]] ... 一路到0就行了
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <stack>
using namespace std;
#define MAXN 400005
char str[MAXN] ;
int f[MAXN] ;
void get_nex( char * str , int len ){
f[1] = f[0] = 0 ;
int j;
for( int i = 1 ; i < len ; i ++ ) {
j = f[i] ;
while( j && str[i] != str[j] ) j = f[j] ;
if( str[i] == str[j] ) f[i+1] = j + 1 ;
else f[i+1] = 0 ;
}
}
int main(){
while( scanf( "%s" , str ) != EOF ) {
int len = strlen( str ) ;
get_nex( str , len ) ;
stack<int> s ;
s.push( len ) ;
while( f[len] ) {
s.push( f[len] ) ;
len = f[len] ;
}
bool first = true ;
while( !s.empty() ) {
printf( first?"%d" :" %d" , s.top() ) ;
s.pop() ;
first = false ;
}
puts( "" ) ;
}
return 0 ;
}给你两个字符串, 第一个字符串中取出两段来,有多少种方式可以组成第二个字符串。取出来的两段可以重叠
那么我们先统计一下cnt1[i] 表示第一个字符串中有多少子串是第二个字符长度为i的子串
计算的时候我们先根据匹配结果去统计,即到匹配到一次长度为i的就cnt1[i] ++ ; 然后因为s[0...i-1] 出现一次的话,那么s[0...f[i]-1]肯定也要出现一次,所以我们统计到cnt1[i] 的时候,要cnt1[f[i]] += cnt1[i]
当然出现一次 s[0...i-1]更短的也会出现过,但是我们计算到cnt1[i]的时候并不用统计,因为这些会在cnt1[f[i]]统计的时候加上
然后两个字符串反过来在匹配一遍,统计出cnt2[i]
那么最后答案就是Σ( cnt1[i] * cnt2[len-i] ) 了
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
#define MAXN 100001
typedef long long LL ;
char s[MAXN] ;
char t[MAXN] ;
char tmp[MAXN] ;
int cnt1[MAXN] , cnt2[MAXN] ;
int f[MAXN] ;
void get_nex( char * str , int len ) {
f[0] = f[1] = 0 ;
int j ;
for( int i = 1 ; i < len ; i ++ ) {
j = f[i] ;
while( j && str[i] != str[j] ) j = f[j] ;
if( str[i] == str[j] ) f[i+1] = j + 1 ;
else f[i+1] = 0 ;
}
}
void KMP( char * str1 , int len1 , char * str2 , int len2 , int * cnt ) {
int j = 0 ;
for( int i = 0 ; i < len1 ; i ++ ) {
while( j && str1[i]!=str2[j] ) j = f[j] ;
if( str1[i] == str2[j] ) {
j ++ ; cnt[j] ++ ;
}
if( j == len2 ) j = f[j] ;
}
}
void stringrev( char * s ) {
int len = strlen( s ) ;
strcpy( tmp , s ) ;
for( int i = 0 ; i < len ; i ++ ) {
s[len-i-1] = tmp[i] ;
}
s[len] = 0 ;
}
int main(){
int cas ;
scanf( "%d" , &cas ) ;getchar() ;
while( cas -- ) {
gets(s);
gets(t);
int len1 , len2 ;
len1 = strlen( s ) , len2 = strlen( t ) ;
get_nex( t , len2 ) ;
memset( cnt1 , 0 ,sizeof(cnt1) ) ;
KMP( s , len1 , t , len2 , cnt1 ) ;
for( int i = len2 ; i >= 1 ; i -- ) {
cnt1[f[i]] += cnt1[i] ;
}
stringrev( s ) ;
stringrev( t ) ;
get_nex( t , len2 ) ;
memset( cnt2 , 0 , sizeof(cnt2) ) ;
KMP( s , len1 , t , len2 , cnt2 ) ;
for( int i = len2 ; i >= 1 ; i -- ) {
cnt2[f[i]] += cnt2[i] ;
}
LL ans = 0 ;
for( int i = 1 ; i < len2 ; i ++ ) {
ans += (LL)cnt1[i] * cnt2[len2-i] ;
}
printf( "%lld\n" , ans ) ;
}
return 0 ;
}给你两个序列 ,要求匹配模式串 ,怎么算匹配上呢 , 就是两个区间中各个数的相对名次是一样的
比如a[i]的名次比哪些小,和哪些一样,都要匹配上
我们预处理出每个数前面有几个比它小的和几个小于等于它的,然后改写下字符匹配上的标准就行了
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <vector>
using namespace std;
int n , m , s ;
int a[100005] , b[100005] ;
int sa[100005][30] ;
int sb[100005][30] ;
int f[100005] ;
vector<int> ans ;
void get_nex( int * a , int (*p)[30] , int len ){
f[0] = f[1] = 0 ;
int j ;
for( int i = 1 ; i < len ; i++ ) {
j = f[i] ;
while( j && ( p[j+1][a[j]] != p[i+1][a[i]] - p[i-j][a[i]] || p[j+1][a[j]-1] != p[i+1][a[i]-1] - p[i-j][a[i]-1] ) ) j = f[j] ;
if( p[j+1][a[j]] == p[i+1][a[i]] - p[i-j][a[i]] && p[j+1][a[j]-1] == p[i+1][a[i]-1] - p[i-j][a[i]-1] ) f[i+1] = j + 1 ;
else f[i+1] = 0 ;
}
}
void KMP( int * a , int (*p)[30] , int len1 , int *b , int (*q)[30] , int len2 ) {
int j = 0 ;
for( int i = 0 ; i < len2 ; i ++ ) {
while( j && ( p[j+1][a[j]] != q[i+1][b[i]] - q[i-j][b[i]] || p[j+1][a[j]-1] != q[i+1][b[i]-1] - q[i-j][b[i]-1] ) ) j = f[j] ;
if( p[j+1][a[j]] == q[i+1][b[i]] - q[i-j][b[i]] && p[j+1][a[j]-1] == q[i+1][b[i]-1] - q[i-j][b[i]-1] ) j ++ ;
if( j == len1 ) ans.push_back( i - len1 + 2 ) , j = f[j] ;
}
}
int main(){
while( scanf( "%d%d%d" , &n , &m , &s ) != EOF ) {
for( int i = 0 ; i < n ; i ++ ) {
scanf( "%d" , &a[i] ) ;
sa[i+1][a[i]] ++ ;
for( int j = 1 ; j <= s ; j ++ )
sa[i+1][j] += sa[i][j] ;
}
for( int i = 0 ; i < n ; i ++ ) {
for( int j = 1 ; j <= s ; j ++ )
sa[i+1][j] += sa[i+1][j-1] ;
}
for( int i = 0 ; i < m ; i ++ ) {
scanf( "%d" , &b[i] ) ;
sb[i+1][b[i]] ++ ;
for( int j = 1 ; j <= s ; j ++ )
sb[i+1][j] += sb[i][j] ;
}
for( int i = 0 ; i < m ; i ++ ) {
for( int j = 1 ; j <= s ; j ++ )
sb[i+1][j] += sb[i+1][j-1] ;
}
get_nex( b , sb , m ) ;
ans.clear() ;
KMP( b , sb , m , a , sa , n ) ;
printf( "%d\n" , ans.size() ) ;
for( int i = 0 ; i < ans.size() ; i ++ ) {
printf( "%d\n" , ans[i] ) ;
}
}
return 0 ;
}原文地址:http://blog.csdn.net/lishaozhe1024/article/details/38454233