所有下標均從1開始
最小表示法
給定一個串,求字典序最小的循環同構。
我們把串複製一遍接在後面,然後求出開始的長爲的子串中最小的
先設
然後暴力找出和往後匹配的第一個不同的位置,記爲和
如果,說明比優,所以不是最優解;然後發現比優,所以不是最優解……這樣可以讓直接跳到。
同理
如果,隨便讓一個即可
兩個指針都不能超過,一個超過之後另一個就是答案
因爲所有位置都會被遍歷,而最優解一定不會被丟掉,所以正確性可以保證。
複雜度顯然是
#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很像所以應該叫擴展馬拉車
解決的問題是給兩個串,求 的每個後綴和 的最長公共前綴
先把接在後面,中間加個#
之類的東西 把這個串記爲
然後設表示的從開始的後綴和(也可以是)的最長公共前綴
並且設公共前綴擴展到的最右位置爲,取到這個最大值的爲
然後從開始遍歷(因爲沒有意義還會把算法搞砸)
如果
因爲上下橙色位置相同,所以,當然要和取
如果,不管
然後暴力擴展,更新,沒了
複雜度顯然
#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 ,如果,那麼是LW
對於的後綴,它比大,所以一定不是最小的;
對於,因爲,所以
對於,因爲,所以
所以是最小的
所以LW可以遞歸定義:
- 單個字符是LW
- 多個字典序遞增的LW順次拼接後是LW
性質2 LW的前綴仍是LW
考慮將原串不斷丟掉最後的字符 那麼會產生一個空後綴,將它刪掉
然後前面的後綴相對大小不會變,所以仍然是LW
性質3 一個LW將最後一個字符變大後仍是LW
只有最後一個只包含一個字符的後綴變大,前面大小關係不變
性質4 任意字符串存在且僅存在一種分解方式,使得所有均爲LW且單調不增
證明是不可能的,這輩子都是不可能的
把性質4中的分解稱爲Lyndon分解
接下來要講的就是線性求Lyndon分解的Duval算法
首先三個指針,表示以前的分解已經固定,現在處理第個字符,一會兒說
即爲,其中爲LW且單調不增
爲,其中是LW,是的可空前綴
也就是一個LW不斷循環,最後一個循環節可以不完整
別問爲啥,問就是歸納法
現在把加在後面,如果要繼續循環,應該加的是,我們把這個應該跟的位置記爲
如果,說明循環正常,繼續往後
如果,根據性質3,最後一個不完整的循環節加上是個LW並且比前面的都大,不斷向前合併發現整段都是LW。所以將一長串合併成新的,即令
如果 不管和大小關係,反正後面怎麼加怎麼都會小於,所以沒啥事了,把所有固定下來
#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;
}
我 華 燈 宴 呢