题目描述
给你一个长度为N的串,求每个前缀的字典序最小的后缀的位置。
1
≤
N
≤
1
e
6
1\leq N \leq 1e6
1≤N≤1e6
分析
这个题可以用SAM做,但是会被卡,所以考虑用Lyndon分解来做
不懂Lyndon分解的可以看看我巨佬学弟博客的学习笔记
发现对于Lyndon分解完后的一个Lyndon串,整个Lyndon串的最小前缀肯定就是这个Lyndon串的开头。
我们可以对每一个Lyndon串做一次exkmp,这样假设从后往前扫,假设一个位置是
i
i
i,然后和这个串前缀相同的长度是
e
x
t
e
n
d
[
i
]
extend[i]
extend[i],那么他就可以对所有
[
i
,
i
+
e
x
t
e
n
d
[
i
]
)
[i,i+extend[i])
[i,i+extend[i])中没有记录过答案的串记录答案。
其实也可以在做Lyndon分解中直接处理,我们假设
k
k
k是要拓展的字符,
j
j
j是
k
k
k的在Lyndon串中相对应的位置,统计答案的时候,假设:
s
[
k
]
>
s
[
j
]
s[k] > s[j]
s[k]>s[j]那么肯定
k
k
k这个位置可以和前面的Lyndon串合并,形成新的Lyndon串,所以记录答案
p
[
k
]
=
i
p[k] = i
p[k]=i
s
[
k
]
=
s
[
j
]
s[k] = s[j]
s[k]=s[j]由于
s
[
i
,
j
]
s[i,j]
s[i,j]和
s
[
k
−
(
j
−
i
)
,
k
]
s[k-(j-i),k]
s[k−(j−i),k]是一样的,那么
k
k
k这个位置的答案就相应继承
j
j
j的答案就好了,
p
[
k
]
=
p
[
j
]
+
(
k
−
j
)
p[k] = p[j]+(k-j)
p[k]=p[j]+(k−j)
最后如果在
i
i
i移动后,新建的一个Lyndon串中,
p
[
i
]
=
i
p[i]=i
p[i]=i就好了
代码
#include <bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define dep(i,a,b) for(int i=(a);i>=(b);--i)
#define fi first
#define se second
#define CL clear
#define MP make_pair
#define PB push_back
#define int long long
const int N = (int) 1e6 + 10;
const int mod = (int) 1e9+7;
using namespace std;
typedef long long ll;
inline int rd() {
int p=0; int f=1; char ch=getchar();
while(ch<'0' || ch>'9'){if(ch=='-') f*=-1; ch=getchar();}
while(ch>='0' && ch<='9'){p=p*10+ch-'0'; ch=getchar();}
return p*f;
}
char s[N]; int p[N];
signed main() {
int t = rd();
while(t--) {
scanf("%s",s+1); int n = strlen(s+1);
for(int i=1;i<=n;) {
int j = i; int k = i+1; p[i] = i;
while(j<=n && s[j] <= s[k]) {
if(s[j] < s[k]) j = i,p[k] = i;
else p[k] = p[j] + k - j , j++;
k++;
}while(i<=j) i+=k-j;
}
int bas = 1; int ans = 0;
for(int i=1;i<=n;++i) ans = (ans + bas * p[i] % mod) % mod , bas = bas * 1112 % mod;
printf("%lld\n",ans);
for(int i=1;i<=n;++i) p[i] = 0;
}
return 0;
}

本文介绍了一种使用Lyndon分解解决求字符串每个前缀的字典序最小后缀位置的问题的方法。通过Lyndon分解和exKMP算法,文章详细解释了如何高效地找到每个前缀的最小后缀,避免了使用SAM算法可能遇到的瓶颈。

被折叠的 条评论
为什么被折叠?



