Lyndon分解
这是什么
Lyndon串:对于一个串 s 1 … n s_{1…n} s1…n的所有后缀, s 1 … n s_{1…n} s1…n的字典序最小,此时,我们称 s 1 … n s_{1…n} s1…n是Lyndon串。
近似Lyndon串:设一个串s是Lyndon串,s’是s的前缀,那么形如ssss’的形式的串称之为近似Lyndon串。
Lyndon分解:将一个串拆成若干个Lyndon串的分解方式。
例如:cuucuucud拆成cuu,cuu,cud
怎么求
设 i 是目前的起始位置,令j = i, k = i + 1,比较 s j s_{j} sj与 s k s_{k} sk的大小:
1. 如果 s j = s k s_{j} = s_{k} sj=sk,那么说明在这一位置无法比较出大小,继续向后比较,j, k同时向后移动。
2. 如果 s j < s k s_{j} < s_{k} sj<sk,那么说明 s u f k − ( j − i ) suf_{k - (j - i)} sufk−(j−i)比 s u f i suf_{i} sufi字典序大,且其中以 s u f k − ( j − i ) … … s u f k suf_{k - (j - i)}……suf_{k} sufk−(j−i)……sufk都要对应地比 s u f i … … s u f j suf_{i}……suf_{j} sufi……sufj大,那么也就必 s u f i suf_{i} sufi大。此时,将 j 移动到 i,从头开始新一轮的比较。
3. 如果 s j > s i s_{j}>s_{i} sj>si,那么说明 s u f k − ( j − i ) suf_{k - (j - i)} sufk−(j−i)比 s u f i suf_{i} sufi字典序小,此时意味着当前Lyndon串再延续下去就不合法了。此时,终止内层循环。
在每一轮循环的结束,我们将得到一个近似Lyndon串 s i … k − 1 s_{i…k-1} si…k−1,其中真正的Lyndon串的大小为 k - j ,那么我们每隔 k - j 位做一次分割即可。
分割完成后,将 i 跳到下一个起始位置即可(注意近似Lyndon串的"零头"要重新做)。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#define LL long long
using namespace std;
const int N = 5e6 + 10;
char s[N];
int main()
{
int ans = 0, n;
scanf("%s", s + 1);
n = strlen(s + 1);
for(int i = 1; i <= n; )
{
int j = i;
int k = i + 1;
while(k <= n && s[j] <= s[k])
{
if(s[j] == s[k]) j++;
else j = i;
k++;
}
while(i <= j)
{
i += k-j;
ans = ans ^ (i - 1);
}
}
cout<<ans;
return 0;
}
有什么用
求最小后缀
HDU-6761
题意:对于一个串的每一个前缀,求其最小后缀。
思路:求串的Lyndon分割,如果 s j < s k s_{j} < s_{k} sj<sk那么跳回到当前近似Lyndon串的串首,否则,由 j 位置顺延一个Lyndon串(也就是回到当前近似Lyndon串的最后一个循环节的起始位置)。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#define LL long long
using namespace std;
const int N = 1e6 + 10;
const LL mod = 1e9 + 7;
LL f[N], d[N];
char s[N];
int main()
{
f[1] = 1;
for(int i = 2; i <= 1e6; i++)
f[i] = f[i - 1] * (LL)1112 % mod;
int T;
scanf("%d", &T);
while(T)
{
T--;
LL ans = 0;
scanf("%s", s + 1);
int n = strlen(s + 1);
d[1] = 1;
for(int i = 1; i <= n;)
{
int j = i;
int k = i + 1;
while(k <= n && s[j] <= s[k])
{
if(s[j] == s[k]) d[k] = d[j] + k - j, j++; //顺延一个串的位置
else d[k] = i, j = i; //回串首
k++;
}
d[k] = k; //下一个串的开始
while(i <= j) i += k - j;
}
for(int i = 1; i <= n; i++) ans = (ans + d[i] * f[i] % mod) % mod;
cout<< ans <<endl;
}
return 0;
}