JB hates solving string problems. Therefore, when his friend Potato gives him a string problem to solve, he immediately gives it to you and continues playing Genshin Impact, the greatest game in the world.
You are given a string and then for each nonempty prefix, you need to find the largest substring in lexicographical order and point out the leftmost occurrence of the largest substring.
Input
The only line contains a string S (1≤∣S∣≤1e6)(1≤∣S∣≤1e6), which consists of lowercase Latin letters, a to z.
Output
Output ∣S∣ lines, the i-th of which contains two integers li and ri, indicating the leftmost occurrence of the largest substring in the prefix of length i.
题目大意
JB讨厌解决字符串问题。因此,当他的朋友土豆给他一个字符串问题要解决时,他会立即把它给你,并继续玩Genshin Impact,世界上最伟大的游戏。
给定一个字符串,然后对于每个非空前缀,您需要按照字典顺序找到最大的子串,并指出最大子串的最左边位置。
例如对于样例1的第二个位置'o',依题目要求有两个字串“po"和"o",字典序更大的是"po",则该子串的范围为[1,2]因此输出其左右边界1,2.
总结:题目要求处理一个字符串 S,并对于每一个非空前缀,找出该前缀中按字典序排列的最大子字符串,并指出这个最大子字符串的左、右区间的位置。
题解
发现这道题很像Lyndon分解的形式,但是题目要求的是求出所有前缀中的最大子字符串。所以考虑Lyndon的逆用,就是只改变求Lyndon分解串中的比较过程,把大于变成小于,小于变成大于即可。
下面是代码详解。
-
输入处理:程序首先从标准输入中读取一个字符串
S
,并计算该字符串的长度n
。 -
初始化:定义一个整型数组
ans
,大小为n
,用于存储每个字符的最小划分长度。数组的初始值为 0,表示尚未被赋值。 -
Duval 算法:
- 算法通过两个指针
i
和k
来遍历字符串。i
表示当前处理的起始位置,j
是一个辅助变量,用于标记字符串的基本重复段。 - 从
i
开始向后扫描,找到所有和s[i]
(当前字符) 相关的字符,以确定它们是否属于同一个划分。 - 每当找到一个字符与
s[j]
相等,且没有被赋值ans[k]
,就将其赋值为i + 1
,即当前划分的起始位置。 - 如果发现
s[k] < s[j]
,则表示出现了新的小字母,更新j
到i
,然后增加j
。 - 最后,通过一个循环
while(i <= j)
来更新i
,使其跳过已处理的部分。
- 算法通过两个指针
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
char S[(int)1e6+5];
vector<int>gap;
int ans[(int)1e6+5];
void Duval(char *s,int n){
for(int i=0,j,k;i<n;){
if(!ans[i])ans[i]=i+1;
for(j=i,k=i+1;k<n&&s[k]<=s[j];k++){
if(!ans[k])ans[k]=i+1;
if(s[k]<s[j])j=i;
else ++j;
}
while(i<=j){
i+=k-j;
}
}
}
int main(){
scanf("%s",S);
int n=strlen(S);
Duval(S,n);
for(int i=0;i<n;i++){
cout<<ans[i]<<" "<<i+1<<endl;
}
return 0;
}