刷了3天的字符串匹配题。
为了下面继续切题,小的先小结一些。
字符串匹配的最基础算法是枚举(n^2)。
高深一点的是KMP。
KMP在数据结构课上学过,由于老师只是负责教学,不负责解答他不懂的问题,于是KMP就这么被我搁置一边了。ACM这么多年了,一直不懂这些基础的算法,实在有愧与心。
于是乎专程学习了一下KMP。
先说说KMP的主要思想。KMP用于模式串的匹配。
下面看看一个字符串:(1)AACAACAAB;
我们需要查找的串为:(2)AACAAB;
首先顺序匹配:
(1)AACAACAAB
(2)AACAA
到这里都顺利匹配上了,我们肉眼观察当(2)继续匹配时,'B'和'C'是不匹配的。那么怎样滑动呢?
对于串(2)我们可以发现'B'之前的字符'AA',与串(1)的'C'前面的两个字符是一样的,那么可以这么滑动....
(1)AACAACAAB
(2) AA
然后继续匹配发现完全匹配了......
好了那么怎么滑动呢?构建一个next数组,记录滑动下标。
可以用一句话来说明:
在J字符的左边有[0,I-1]与[J-I-1,J-1]相同的话,下次J失配时,就可以滑动到I。
因为是滑动到J才失配,也就是说,J之前的所有字符串都是和主串相匹配的。由此,只要在本串中找到前缀和主串相匹配的(一定是部分匹配)选择滑动就可以了。
下面是构建next的函数。T是模式串
void setNext()
{
int j=0,k=-1;
next[0]=-1;
while( j<lenT )
{
if( k==-1||t[j]==t[k] )
next[++j]=++k;
else
k=next[k];
}
}
当失配则回退,匹配则赋值继续前进。
下面是KMP的匹配模版
int kmp()
{
int i=0,j=0;
cnt=0;
while( i<lenS&&j<lenT )
{
if( j==-1 || s[i]==t[j] )
i++,j++;
else
j=next[j];
}
if( j>lenT )
return i-lenT;
else
return -1;
}
匹配则继续,失配则滑动。
KMP主要用来解决的问题:
1.主串中模式串出现的位置
2.主串中出现模式串的次数
3.主串分割成多少个模式串
4.模式串中前缀的循环次数
以上为KMP....... 写得不好啊........
好了接下来Manacher;
这个算法主要是用于计算回文串。运用了回文串的性质。
假设我们有一个回文串以id为中心,p[id]为以id为中心的回文串的半径。
下面给一个回文串:
id p[id]
|<----|----->|
CABAAKAABAA
可以看出回文串为ABAAKAABA中心为K。
好了我们以mx=id+p[id],以id为中心的回文字符串的最右控制范围。
现在看K的右边那个字符串'B'。这个B实在mx之内的,所以还是受到了id的控制!
所以这个B的性质与B关于id的对称点左边的B有关。为啥?因为是回文嘛~两边对称。
通过肉眼,p['B']=1;所以右边这个B的左右两边也和左边的B相似。
但是仅限于当右边的B的右边界还在mx内时。
为啥?
看下面
AABAAKAABAC
这个字符串左边的p[B]=2;而右边的B显然没有这么广的控制范围,因为超过了mx的控制范围了。
所以右边p[B]的控制范围在与右边界的距离,和对称点的控制范围内取一个最小的就可以了。
但是对于原来的字符串,右边的B实际范围是还可以拓展的。所以继续拓展就好了。
拓展完后会发现新的回文串的最右边界超过了mx,此时记录更新就好了。
从左到右扫完后,取出最大的p[id]就好。再处理一下就好了。
为了避免判断奇偶性,在串中插入不常用字符'#','$'什么的.... 就这样。
#include<iostream>
#include<cstdio>
#include<string.h>
using namespace std;
int p[2222222];
char str[1111111],str1[2222222];
int len;
void Init()
{
str1[0]='$';
str1[1]='#';
len=2;
for( int i=0;str[i]!=0;i++ )
{
str1[len++]=str[i];
str1[len++]='#';
}
str1[len]=0;
}
int main()
{
int T=0;
while( scanf("%s",&str)!=EOF )
{
if( strlen(str)==3 && str[0]=='E' && str[1]=='N' && str[2]=='D' )
break;
//memset( str1,0,sizeof(str1) );
memset( p,0,sizeof(p) );
Init();
int id,mx=0;
for( int i=1;i<len;i++ )
{
if( mx>i )
p[i]=min(p[(id<<1)-i],mx-i);
else
p[i]=1;
while( str1[i-p[i]]==str1[i+p[i]] )
p[i]++;
if( mx<i+p[i] );
{
mx=i+p[i];
id=i;
}
}
printf( "Case %d: ",++T );
int ans=0;
for( int i=1;i<len;i++ )
ans=max(p[i],ans);
printf( "%d\n",ans-1 );
}
return 0;
}
好吧下面继续讲==
所谓TireTree就是字典树,字母树。从根节点开始,每个节点代表一个字母,单词的第K个字母在树的第K层。
这只是一种数据结构。实现也不难。但却是后缀树与AC自动机的基础。
不多说,直接上模版。
#include<iostream>
#include<string>
#include<cstdio>
#define MAX 10
using namespace std;
char s[11111][11];
int allocp;
struct TireNode
{
int nCount;
TireNode *next[MAX];
};
TireNode Memeroy[1111111];
void InitTire( TireNode **root )
{
*root=NULL;
}
TireNode *CreateTire()
{
int i;
TireNode *p=&Memeroy[allocp++];
p->nCount=1;
for( int i=0;i<MAX;i++ )
p->next[i]=NULL;
return p;
}
void InsertTire( TireNode **root,char *s )
{
int i=0,k;
TireNode *p;
if( !(p=*root) )
p=*root=CreateTire();
while( s[i] )
{
k=s[i++]-'0';
if( p->next[k] )
p->next[k]->nCount++;
else
p->next[k]=CreateTire();
p=p->next[k];
}
}
bool SearchTire( TireNode **root,char *s )
{
int i=0,k;
TireNode *p=*root;
int cnt=0;
while( s[i] )
{
k=s[i++]-'0';
cnt=p->next[k]->nCount;
p=p->next[k];
}
if( cnt==1 )
return false;
else
return true;
}
int main()
{
int T;
scanf( "%d",&T );
while( T-- )
{
allocp=0;
TireNode *root;
root=NULL;
int len=0;
scanf( "%d",&len );
for( int i=0;i<len;i++ )
{
scanf( "%s",&s[i] );
InsertTire(&root,s[i]);
}
bool found=true;
for( int i=0;i<len;i++ )
{
if( SearchTire(&root,s[i]) )
{
found=false;
break;
}
}
if( found==false )
printf( "NO\n" );
else
printf( "YES\n" );
}
return 0;
}
累了。。。。AC自动机明天在写吧。。。
可以这么理解AC自动机就是在一棵字典树树上进行KMP......
先模版之.......
#include<iostream>
#include<cstdio>
#include<string.h>
#define MAX 26
using namespace std;
int root,tot;
struct node
{
int fail;
int cnt;
int next[MAX];
void init()
{
memset( next,0,sizeof(next) );
fail=-1;cnt=0;
}
}Tire[5555555];
int queue[5555555];
void init(){
root=tot=0;
Tire[root].init();
}
void insert( int root,char *s ){
int p=root;
int i=0,k;
while( s[i] )
{
k=s[i++]-'a';
if( !Tire[p].next[k] )
{
Tire[++tot].init();
Tire[p].next[k]=tot;
}
p=Tire[p].next[k];
}
Tire[p].cnt++;
}
void build_ac_automation()
{
int head,tail;
head=tail=0;
queue[tail++]=root;
while( head<tail )
{
int cur=queue[head++];
for( int i=0;i<MAX;i++ )
{
if( Tire[cur].next[i] )
{
int son=Tire[cur].next[i];
int p=Tire[cur].fail;
if( cur==root )
Tire[son].fail=root;
else
Tire[son].fail=Tire[p].next[i];
queue[tail++]=son;
}
else
{
int p=Tire[cur].fail;
if( cur==root )
Tire[cur].next[i]=root;
else
Tire[cur].next[i]=Tire[p].next[i];
}
}
}
}
int query( char *s )
{
int i=0,k,p=root;
int ret=0;
while( s[i] )
{
k=s[i++]-'a';
while( !Tire[p].next[k]&&p!=root )
p=Tire[p].fail;
p=Tire[p].next[k];
if(p==-1)p=0;
int temp=p;
while( temp!=root&&Tire[temp].cnt!=-1 )
{
ret+=Tire[temp].cnt;
Tire[temp].cnt=-1;
//sTire[temp].cnt=0;
temp=Tire[temp].fail;
}
}
return ret;
}
char str[1111111];
int main(){
int T;
scanf( "%d",&T );
while( T-- ){
init();
int N;
scanf( "%d",&N );
while( N-- )
{
scanf( "%s",&str );
insert( root,str );
}
build_ac_automation();
scanf( "%s",&str );
printf( "%d\n",query(str) );
//system("pause");
}
return 0;
}