最近在学习字符串算法,学到KMP和AC自动机总有种似懂非懂的感觉,这个题的思路也是看的STD
KMP
个人感觉KMP的精髓就是失配函数,在当前匹配失败的情况下不必重头推倒再来,而是从特定的位置。i位置的失配函数为f[i],可表示i位置失配后去f[i]位置找,是因为f[i]位置之前的模板串与i位置前的模板串的等长后缀是相等的,这样做下去当发现整个模板串都遍历完成,就是找到了模板串作为目标串子串的结束位置)
AC自动机
AC自动机是用来处理多个模板串的,所以就用到了Trie树。个人认为AC自动机是Trie树和KMP结合,或者说是特殊版的Trie树。当用目标串在Trie树中查找时,如果走到当前节点A后,发现没有目标串下一个字符a的节点,就沿着失配边一直跳,直到到根或者到有子节点a的节点B,B就是需要重新开始的起点
不必向上找失配边的操作
算法竞赛入门经典这本书上写了两种做法,这个题需要用第二种,就是getfail时,枚举节点A所有可能的下一个节点B,如果A走不到B,那么不是将B跳过,因为这样的话查找的时候从A找不到节点B还要跳失配边直到根或者到有子节点B的节点。
所以搞一个类似于并茶几路径压缩的操作,即A走不到B,就把SON【A】【B】设为A沿着失配边向上的点的对应B节点的位置,就相当于自动跳了一次失配边
这题做法不写了,算法竞赛入门经典(蓝书)P218
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define fr(i,s,t) for (i=s;i<=t;i++)
#define Clear(a) memset(a,0,sizeof(a))
using namespace std;
int K,N,L,Size,son[20000][70],f[2000];
char S[200];
double pro[200],ans,dp[20000][200];
bool v[2000],vis[20000][200];
int id(char c){
if (c>='A'&&c<='Z') return c-'A';
if (c>='a'&&c<='z') return 26+c-'a';
return 52+c-'0';
}
void Insert(){
int rt=0,d,i;
for (i=0;i<strlen(S);i++){
d=id(S[i]);
if (!son[rt][d]) son[rt][d]=++Size;
rt=son[rt][d];
}
v[rt]=1;
}
void Getfail(){
queue <int> Q;
int i,u,y,x;
for (i=0;i<63;i++){
u=son[0][i];
if (u) Q.push(u);
}
while (!Q.empty()){
x=Q.front(); Q.pop();
for (i=0;i<63;i++){
u=son[x][i];
if (!u){son[x][i]=son[f[x]][i];continue;}
Q.push(u);
y=f[x];
while (y&&!son[y][i]) y=f[y];
f[u]=son[y][i];
v[u]|=v[f[u]];
}
}
}
double Dfs(int x,int L){
if (!L) return 1.0;
if (vis[x][L]) return dp[x][L];
vis[x][L]=1;
double &res=dp[x][L];
res=0;
for (int i=0;i<63;i++)
if (!v[son[x][i]]&&pro[i])
res+=pro[i]*Dfs(son[x][i],L-1);
return res;
}
void Work(int Num){
Clear(pro); Clear(dp); ans=0;
Clear(son); Size=0; Clear(vis); Clear(f); Clear(v);
scanf("%d",&K);
int i;
fr(i,1,K)
cin>>S,Insert();
scanf("%d",&N);
fr(i,1,N){
char ch; cin>>ch;
scanf("%lf",&pro[id(ch)]);
}
Getfail();
scanf("%d",&L);
ans=Dfs(0,L);
printf("Case #%d: %0.6lf\n",Num,ans);
}
int main(){
int T,i;
scanf("%d",&T);
fr(i,1,T) Work(i);
return 0;
}
。