幾個冷門字符串算法的學習筆記(最小表示法,exKMP,Lyndon Word)

所有下標均從1開始

最小表示法

給定一個串,求字典序最小的循環同構。

我們把串複製一遍接在後面,然後求出[1,N][1,N]開始的長爲NN的子串中最小的

先設i=1,j=2i=1,j=2

然後暴力找出iijj往後匹配的第一個不同的位置,記爲i+ki+kj+kj+k

如果Si+k<Sj+kS_{i+k}<S_{j+k},說明iijj優,所以jj不是最優解;然後發現i+1i+1j+1j+1優,所以j+1j+1不是最優解……這樣可以讓jj直接跳到j+k+1j+k+1

Si+k>Sj+kS_{i+k}>S_{j+k}同理

如果i=ji=j,隨便讓一個+1+1即可

兩個指針都不能超過NN,一個超過之後另一個就是答案

因爲所有位置都會被遍歷,而最優解一定不會被丟掉,所以正確性可以保證。

複雜度顯然是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,T,求 TT的每個後綴和SS 的最長公共前綴

先把SS接在TT後面,中間加個#之類的東西 把這個串記爲AA

然後設pip_i表示AA的從ii開始的後綴和TT(也可以是AA)的最長公共前綴

並且設公共前綴擴展到的最右位置爲mxmx,取到這個最大值的iixx

然後ii22開始遍歷(因爲p1p_1沒有意義還會把算法搞砸)

如果i<mxi<mx

在這裏插入圖片描述
因爲上下橙色位置相同,所以pi=pix+1p_i=p_{i-x+1},當然要和mxi+1mx-i+1min\min

如果imxi \geq mx,不管

然後暴力擴展,更新mxmx,沒了

複雜度顯然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,v,如果u<vu<v,那麼u+vu+v是LW

對於vv的後綴,它比vv大,所以一定不是最小的;

對於vv,因爲u<vu<v,所以u+v<vu+v<v

對於(u)+v(u的後綴)+v,因爲u<(u)u<(u的後綴),所以u+v<(u)+vu+v<(u的後綴)+v

所以u+vu+v是最小的

所以LW可以遞歸定義:

  1. 單個字符是LW
  2. 多個字典序遞增的LW順次拼接後是LW

性質2 LW的前綴仍是LW

考慮將原串不斷丟掉最後的字符 那麼會產生一個空後綴,將它刪掉

然後前面的後綴相對大小不會變,所以仍然是LW

性質3 一個LW將最後一個字符變大後仍是LW

只有最後一個只包含一個字符的後綴變大,前面大小關係不變

性質4 任意字符串SS存在且僅存在一種分解方式S=s1+s2+...+snS=s_1+s_2+...+s_n,使得所有sis_i均爲LW且單調不增

證明是不可能的,這輩子都是不可能的

把性質4中的分解稱爲Lyndon分解

接下來要講的就是線性求Lyndon分解的Duval算法

首先三個指針i,j,ki,j,k,表示ii以前的分解已經固定,現在處理第kk個字符,jj一會兒說

[1,i)[1,i)s1+s2+...+sns_1+s_2+...+s_n,其中sis_i爲LW且單調不增

[i,k)[i,k)t+t+...+t+t1t+t+...+t+t_1,其中tt是LW,t1t_1tt的可空前綴

也就是一個LW不斷循環,最後一個循環節可以不完整

別問爲啥,問就是歸納法

現在把SkS_k加在後面,如果要繼續循環,應該加的是SkS_{k-循環節長度},我們把這個kk應該跟的位置記爲jj

如果Sj==SkS_j==S_k,說明循環正常,繼續往後

如果Sj<SkS_j<S_k,根據性質3,最後一個不完整的循環節t1t_1加上SkS_k是個LW並且比前面的tt都大,不斷向前合併發現整段都是LW。所以將[i,k][i,k]一長串合併成新的tt,即令j=ij=i

如果Sj>SkS_j>S_k 不管t1t_1SkS_k大小關係,反正後面怎麼加怎麼都會小於tt,所以沒tt啥事了,把所有tt固定下來

模板題

#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;
}

我 華 燈 宴 呢

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章