几个冷门字符串算法的学习笔记(最小表示法,exKMP,Lyndon Word)
所有下標均從1開始
最小表示法
給定一個串,求字典序最小的循環同構。
我們把串復制一遍接在后面,然后求出[1,N][1,N][1,N]開始的長為NNN的子串中最小的
先設i=1,j=2i=1,j=2i=1,j=2
然后暴力找出iii和jjj往后匹配的第一個不同的位置,記為i+ki+ki+k和j+kj+kj+k
如果Si+k<Sj+kS_{i+k}<S_{j+k}Si+k?<Sj+k?,說明iii比jjj優,所以jjj不是最優解;然后發現i+1i+1i+1比j+1j+1j+1優,所以j+1j+1j+1不是最優解……這樣可以讓jjj直接跳到j+k+1j+k+1j+k+1。
Si+k>Sj+kS_{i+k}>S_{j+k}Si+k?>Sj+k?同理
如果i=ji=ji=j,隨便讓一個+1+1+1即可
兩個指針都不能超過NNN,一個超過之后另一個就是答案
因為所有位置都會被遍歷,而最優解一定不會被丟掉,所以正確性可以保證。
復雜度顯然是O(N)O(N)O(N)
模板題
#include <iostream> #include <cstdio> #include <cstring> using namespace std; char s[10005]; int main() {int T;scanf("%d",&T);while (T--){scanf("%s",s);int n=strlen(s);int i=0,j=1;while (i<n&&j<n)for (int k=0;;k++){if (s[(i+k)%n]!=s[(j+k)%n]){if (s[(i+k)%n]>s[(j+k)%n])i+=k+1;else j+=k+1;if (i==j) j++;break;}if (k==n) goto end;}end:printf("%d\n",min(i,j)+1);}return 0; }(遠古代碼,和上面講的略有不同,僅供參考)
擴展KMP
官方名稱應該叫Z算法,不知道為啥傳到國內就變成擴展KMP了
但實際上思想和manacher很像所以應該叫擴展馬拉車
解決的問題是給兩個串S,TS,TS,T,求 TTT的每個后綴和SSS 的最長公共前綴
先把SSS接在TTT后面,中間加個#之類的東西 把這個串記為AAA
然后設pip_ipi?表示AAA的從iii開始的后綴和TTT(也可以是AAA)的最長公共前綴
并且設公共前綴擴展到的最右位置為mxmxmx,取到這個最大值的iii為xxx
然后iii從222開始遍歷(因為p1p_1p1?沒有意義還會把算法搞砸)
如果i<mxi<mxi<mx
因為上下橙色位置相同,所以pi=pi?x+1p_i=p_{i-x+1}pi?=pi?x+1?,當然要和mx?i+1mx-i+1mx?i+1取min?\minmin
如果i≥mxi \geq mxi≥mx,不管
然后暴力擴展,更新mxmxmx,沒了
復雜度顯然O(∣S∣+∣T∣)O(|S|+|T|)O(∣S∣+∣T∣)
模板題
#include <iostream> #include <cstdio> #include <cstring> #include <cctype> #define MAXN 200005 using namespace std; char s[MAXN],t[MAXN]; int p[MAXN]; int main() {scanf("%s%s",t+1,s+1);int m=strlen(s+1);strcat(s+1,"#");strcat(s+1,t+1);int n=strlen(s+1);for (int i=2,x=0,mx=0;i<=n;i++){p[i]=i<=mx? min(p[i-x+1],mx-i+1):0;while (s[i+p[i]]==s[p[i]+1]) ++p[i];if (i+p[i]-1>mx) x=i,mx=i+p[i]-1;}for (int i=1;i<=n;i++)if (s[i]=='#') puts("");else printf("%d ",i>1? p[i]:m);return 0; }Lyndon Word
定義:一個串是Lyndon Word(以下簡稱LW),當且僅當它本身是自己字典序最小的后綴
下文字符串的比較均為字典序,+為字符串拼接
性質1 兩個LW u,vu,vu,v,如果u<vu<vu<v,那么u+vu+vu+v是LW
對于vvv的后綴,它比vvv大,所以一定不是最小的;
對于vvv,因為u<vu<vu<v,所以u+v<vu+v<vu+v<v
對于(u的后綴)+v(u的后綴)+v(u的后綴)+v,因為u<(u的后綴)u<(u的后綴)u<(u的后綴),所以u+v<(u的后綴)+vu+v<(u的后綴)+vu+v<(u的后綴)+v
所以u+vu+vu+v是最小的
所以LW可以遞歸定義:
性質2 一個LW將最后一個字符變大后仍是LW
只有最后一個只包含一個字符的后綴變大,前面大小關系不變
性質3 任意字符串SSS存在且僅存在一種分解方式S=s1+s2+...+snS=s_1+s_2+...+s_nS=s1?+s2?+...+sn?,使得所有sis_isi?均為LW且單調不增
證明是不可能的,這輩子都是不可能的
把性質3中的分解稱為Lyndon分解
接下來要講的就是線性求Lyndon分解的Duval算法
首先三個指針i,j,ki,j,ki,j,k,表示iii以前的分解已經固定,現在處理第kkk個字符,jjj一會兒說
即[1,i)[1,i)[1,i)為s1+s2+...+sns_1+s_2+...+s_ns1?+s2?+...+sn?,其中sis_isi?為LW且單調不增
[i,k)[i,k)[i,k)為t+t+...+t+t1t+t+...+t+t_1t+t+...+t+t1?,其中ttt是LW,t1t_1t1?是ttt的可空前綴
也就是一個LW不斷循環,最后一個循環節可以不完整。注意這不一定是[i,k)[i,k)[i,k)的Lyndon分解,因為t1t_1t1?不一定是LW
別問為啥,問就是歸納法
現在把SkS_kSk?加在后面,如果要繼續循環,應該加的是Sk?循環節長度S_{k-循環節長度}Sk?循環節長度?,我們把這個kkk應該跟的位置記為jjj
如果Sj=SkS_j=S_kSj?=Sk?,說明循環正常,繼續往后
如果Sj<SkS_j<S_kSj?<Sk?,根據性質1,最后一個不完整的循環節t1t_1t1?加上SkS_kSk?是個LW并且比前面的ttt都大,不斷向前合并發現整段都是LW。所以將[i,k][i,k][i,k]一長串合并成新的ttt,即令j=ij=ij=i
如果Sj>SkS_j>S_kSj?>Sk? 不管t1t_1t1?和SkS_kSk?大小關系,反正后面怎么加怎么都會小于ttt,所以沒ttt啥事了,把所有ttt固定下來,t1t_1t1?作為新的循環節。然后t1t_1t1?這個地方,我們之前以為它會進入循環,然而它沒有,這里面漏了一些信息,所以需要從t1t_1t1?的開頭重新分解
模板題
#include <iostream> #include <cstdio> #include <cstring> #include <cctype> #define MAXN (1<<20)+5 using namespace std; char s[MAXN]; int main() {scanf("%s",s+1);int n=strlen(s+1);for (int i=1;i<=n;){int j=i,k=i+1;while (s[j]<=s[k]){if (s[j]==s[k]) ++j;else j=i;++k;}while (i<=j){printf("%d ",i+k-j-1);i+=k-j;}}return 0; }我 華 燈 宴 呢
總結
以上是生活随笔為你收集整理的几个冷门字符串算法的学习笔记(最小表示法,exKMP,Lyndon Word)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【ZJOI2015】幻想乡 Wi-Fi
- 下一篇: Luat蓝牙指南