学到了个新的操作,调题目调了好久,但是很有意思,分享一哈。
题目链接在此
大致意思就是给定T个字符串,每个字符串尤其长度个数量的前缀,求其每个前缀中字典序最小的后缀的起始位置,并将他们乘上1112的幂加起来对1e9 + 7取模。
题目不难理解,但就是每个字都看懂了,连起来就是不会或者说没有思路。要解决这个问题,一种可行的方法是借助lyndon分解,下面我们就先来看一下lyndon分解是何方神圣。
介绍lyndon分解前,我们需要先了解一个概念:lyndon串。
对于字符串 ,如果 的字典序严格小于 的所有后缀的字典序,我们称 是简单串,或者 Lyndon 串 。也就是说对于一个lyndon串来说,字典序最小的后缀就是其本身。像aaabb
, aabab
等都是lyndon串。
所谓lyndon分解,就是将一个字符串分解成为若干连续的lyndon子串。这些字串满足非严格的单调递减的字典序。也就是说,允许存在相邻的lyndon串相同,但是不允许出现排在后面的lyndon串比前面的串小。这点不难说明,我们假设有lyndon串 si 和 si+1 ,假设
si < si+1
那么容易说明sisi+1也是一个lyndon串,分解时应直接生成sisi+1而非两个字串,因此不会出现上述情况。可以发现,这样的费解存在且唯一。
进行lyndon分解,可以使用Duval算法进行处理,这里简述一下这个算法过程,具体的过程可以参考 IO Wiki Lyndon分解
Duval算法采用贪心的思想,将一个字符串分成三个部分,即:已分解s1,正在处理s2,未处理s3,将字符一个一个处理。因此整个字符串S=s1s2s3
使用三个下标i j k
标记字符串,其中i
表示s2的起始位置,j
表示s3的起始位置即新加入的字符,k
代表着j位字符在s2上一个循环节对应的字符。
每一次,若
s[j] == s[k]
,意味着j对应着s2某个循环节的一个字符,拼在s2的末尾使得其仍满足其近似简单性,使j k
同时向后移动一步。例如s2=aab,s[j] = a,s[k] = a
s[j] > s[k]
,意味着循环节失效,新的字符直接拼到s2的末尾,使得新的s2整体就是一个lyndon串,例如:abab ---> ababc
,此时让k = i,j++
s[j] < s[k]
,此时s[j]拼在s2的末尾就会影响其简单性了,那么就不能继续添加,转而应该将s2中现有的lyndon串全部分解出来变成s1的一部分,其中的lyndon串就是完整的循环节剩下的继续上面的过程。例如,abbabbab + a --> abb|abb| ab + a
前面两个abb就是已经可以确认的lyndon串。但是后面剩下的ab
就不是lyndon串了。不难发现,完整的lyndon串的起始位置都小于等于k
且长度为j - k
。
代码实现:
#include
#include
#include
using namespace std;
vector<string> duval(string str){
int i = 0,j,k,n = str.size();
vector<string> ans;
while(i < n){
k = i;
j = i + 1;
while(j < n && str[k] <= str[j]){
k = str[k] == str[j] ? k + 1 : i;
j++;
}
string s;
while(i <= k){
ans.push_back(str.substr(i,j - k));
i += j - k;
}
}
return ans;
}
int main(){
string str;
cin >> str;
vector<string> ans = duval(str);
for(int i = 0;i < ans.size();i++){
cout << ans[i] << endl;
}
}
借助lyndon分解的思想,就可以处理这道题了。同样是一位一位的加,只需要在分解的过程中对于每一位计算出其所在的lyndon串起始的位置 idx 即可。
还是三种情况:
s[j] = s[k]
,此时该位的下标与其的所在的lyndon串起始位置的差与一个周期前的相同。这么说有点抽象,举个例子就是对于abba + b
,其前面四个字符的idx应该为:1114
中新来的b它与前面的第一个b对应,那么它的 idx 也应与前面的b相对应,它们的idx与其下标的差值,即 idx[j] - j = idx[k] - k
,简单的移一下项就得到了式子:idx[j] =idx[k] + j - k
s[j] > s[k]
,此时整个s2就是循环节,idx[j] = i + 1
(i从0开始,所以加上1)
s[j] < s[k],
此时需要分解s2,若s2完全被分解,即在分解后,i == j
那么这个新的字符就是新的s2的第一个字符,这种情况j的idx不会上面两种情况内被计算,需要额外处理。
代码:
#include
#include
#include
#include
#include
using namespace std;
char str[1000050];
int idx[1000050];
int T,n;
long long ans = 1;
long long pow = 1112;
long long mo = 1000000007;
void duval(){
int i = 0,j,k;
while(i < n){
k = i;
j = i + 1;
while(j < n && str[j] >= str[k]){
if(str[j] == str[k]){
idx[j] = j + idx[k] - k;
k++;
}else{
k = i;
idx[j] = i + 1;
}
j++;
}
while(i <= k){
i += j - k;
}
if(i == j && i < n){
idx[j] = i + 1;
}
}
}
int main(){
cin >> T;
idx[0] = 1;
while(T--){
ans = 0;
pow = 1;
scanf("%s",str);
n = strlen(str);
duval();
for (int i = n - 1; i >= 0; i--) ans = (1112LL * ans + idx[i]) % 1000000007;
cout << ans << endl;
}
}