今天是暑期训练 第七 第六天(中间放了一天假)。
学习字符串简单算法,kmp,AC自动机,trie树等。
----算法模板----
KMP:随手打的,不知道能不能过。
void getnxt(char *W){ memset(nxt,-1,sizeof nxt); int k=-1,wlen=strlen(W); for(int q=0;q<wlen;q++){ while(k!=-1){ if(W[q]==W[k]) break; k=nxt[k]; } nxt[q+1]=++k; } } int KMP(char *W,char *T){ int ans=0; int wlen=strlen(W),tlen=strlen(T); for(int i=0,j=0;i<=tlen;i++,j++){ if(j==-1) continue; if(W[j]!=T[i]||j==wlen){ ans+=(j==wlen?1:0); i--; j=nxt[j]-1; continue; } } return ans; }
AC自动机,trie树:
//trie树 void add(char *s,int num){ int len=strlen(s),x=0; val[0]=' '; for(int pos=0;pos<len;pos++){ if(!trie[x][s[pos]-'a']){ trie[x][s[pos]-'a']=++cnt; val[cnt]=s[pos]; } x=trie[x][s[pos]-'a']; } end[x]=num; return ; } //AC自动机 queue <int> q; void bfs(){ q.push(0); while(!q.empty()){ int now=q.front();q.pop(); for(int i=0;i<=25;i++){ if(!trie[now][i]) continue; if(now==0){ q.push(trie[now][i]); continue; } int j=fail[now]; while(!trie[j][i]&&j) j=fail[j]; fail[trie[now][i]]=trie[j][i]; q.push(trie[now][i]); } } return ; } void search(char *s){ int x=0; int len=strlen(s); for(int pos=0;pos<=len;pos++){ int j=x; while(j){ if(j==fail[j]) exit(-1); if(end[j]) ans[end[j]]++; j=fail[j]; } while(!trie[x][s[pos]-'a']&&x) x=fail[x]; x=trie[x][s[pos]-'a']; } return ; }
-----模拟赛&例题-----
地址:https://cn.vjudge.net/contest/236074
A.POJ 3461
kmp裸题。
B.POJ 2752
有两种想法,但做法一样。
一、看作自己与自己匹配,每次成功匹配的位置即是答案。
(不必真的跑KMP,只需要递归加入答案即可)
二、既然是找相同前缀后缀,那么最长的已经算出,第二长的就是最长的最长相同前缀后缀,以此类推。
(和上一种实现方法相同)
其实我认为,这两种想法的等价性就是KMP的精华所在。
C.POJ 3080
枚举第一个串的字串,然后看看它们是不是其它串的字串。
原来复杂度是O(len^4*n)的,kmp优化后为O(len^3*n+n^2)
D.POJ 2406
我们发现,如果循环节存在,那么这个串的最长相同前缀后缀必然和原串只差一个循环节长。
所以跑nxt后判断输出答案即可。
AC自动机上DP……
开始写了一个dfs,获得10分。
后来考虑优化,发现可以用dp[i][j]表示考虑i位,当前在j的方案数。
接着我们又会发现如果存含可读单词的方案数在AC自动机上不好跑……
所以存不含可读单词的,AC自动机上转移即可。
最后用26^M-Σdp[M][i]即可。
F.HDU 2222
AC自动机裸题。去年居然RE了……
教训:strlen不仅不能在循环里用,也不能在递归函数里用,要传参下去。
T了数百次啊……
附代码:
以后有时间再放吧。