2020-HDU 多校训练赛1011 (Lyndon 分解)

Minimum Index
Time Limit: 3000/1500 MS (Java/Others) Memory Limit: 524288/524288 K (Java/Others)
Total Submission(s): 264 Accepted Submission(s): 56

Problem Description
Let s=s1 s2⋯sn be a string of length n. For any integer k between 1 to n, the kth suffix of s is defined as sk sk+1⋯sn. For example, the 4th suffix of “contest” is “test” and the 1st suffix of “suffix” is “suffix” itself.

One can consider lexicographically smallest suffix of s. Let the kth suffix is lexicographically minimum among all suffixes of s. Conveniently, let’s call such k minimum index of s. Given a string t=t1 t2⋯tn, your task is to calculate the sum of (minimum index of t1 t2⋯ti)⋅1112i−1 over all i=1,2,3,⋯,n. This value may be too large, you are only required to print it modulo 109+7.

Input
The first line of the input contains a single integer T (1≤T≤10 000), the number of test cases. Each of the next T lines contains a non-empty string with at most 106 lowercase English letters. The total length of the input string over all test cases doesn’t exceed 2⋅107.

Output
For each test case, you should print the answer in a single line.

Sample Input

1
aab

Sample Output

1238769

Hint

The minimum index of “a”, “aa”, “aab” is 1, 2, 1 respectively. So the answer is (1+2∙1112+11122) mod (109+7)=1238769.
题意:
给一个字符串,求这个字符串的每个前缀的最小后缀的起始位置ki,然后求这个函数 ki*1112i-1,∀i∈[1,n],的和。

解题思路:
考虑lyndon分解串,lyndon串的定义是:
一个串的最小后缀是它本身的字符串,就是lyndon串。
于是,我们将字符串s分解为s1s2s3…sk,其中∀si是一个lyndon串。
那么 这个时候我们可以发现,∀si,si>=si+1,
为什么呢?假设si<si+1,那么si+si+`<si+1,所以si可以和si+1合并在一起使得si和si+1是一个lybdon串。
当我们得到这个结论之后,是不是应该兴奋起来了呢?
因为,如果我们要求前缀(t1t2…tx)的最小后缀的起始位置,那么我们只需要寻找包含第x位置的字符的字符串si的起始位置就可以了。分解lyndon串的算法:Duval算法(时间复杂度O(N))
以下内容来自:weixin_30786617‘s blog和我的理解。
对于lyndon串a,b,如果a<=b,ab不是一个lyndon串,但是如果有一个串c>b<=a,那么abc就是一个lyndon串。
Duval算法就是处理这样的情况。算法中,更新一个lyndon串的循环节长度,对于一个字符串,我们维护两个指针,j,k。j表示串a中的位置,k表示串b的位置。我们先加入起始位置的字符串,也就是令j为串a的起始位置,然后去寻找后面的字符大于等于此位置的字符,如果等于此位置的字符,那么表示我们找到了一个串b<=a,那么我们将两个指针都后移一位继续比较,如果位置k的字符比位置j的字符要大,那么我们就找到了一个字符串c>b<=a,那么abc就是一个lyndon串,此时我们更新循环节长度,也就是我们令a=abc,再继续去寻找串b和串c的过程,如果我们找到了串a和串b,但是没有找到串c,那么b<=a,此时a就是一个lyndon串,b是一个lyndon串(PS:可能有一些串b是=a的,此时我们确定了串a的循环节长度,以此输出这些长度的字符串就可以了)。如果串b小于串a,那么串b就是一个新的lyndon串的前缀,所以我们继续去从串b开始找新的lyndon串就可以了。

以下是巨巨博客里的算法步骤:
该算法中我们仅需维护三个变量i,j,k

s[1…i−1]=s1s2…sg是固定下来的分解,也就是∀l∈[1,g]sl是Lyndon串且sl>sl+1

s[i…k−1]=t1t2…thv(h>1) 是没有固定的分解,满足t1是Lyndon串,且t1=t2=⋯=th,v是th的(可为空的)真前缀,且有sg>s[i…k−1]

当前读入的字符是s[k],令j=k−|t1|

分三种情况讨论

当s[k]=s[j]时,周期k−j继续保持

当s[k]>s[j]时,合并得到t1<−t1t2…thvs[k]是Lyndon串

当s[k]<s[j]时,t1,t2,…,th的分解被固定下来,算法从v的开头处重新开始

代码:

#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
//std::mt19937 rnd(233);
#define pp pair<int,int>
#define ull unsigned long long
#define ls root<<1
#define rs root<<1|1
//#define int long long
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int NINF = 0xc0c0c0c0;
const int maxn = 2e7+7;
const int Maxn = 1e7+7;
const double eps=1e-6;
const int mod=1e9+7;
ll num[maxn];
char arr[maxn];
signed main(){
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%s",arr+1);
        int len=strlen(arr+1),j,k;
        for(int i=1;i<=len;){
            j=i;k=i+1;
            int l=1;
            num[i]=i;
            //cout<<i<<endl;
            while(k<=len && arr[j]<=arr[k]){
                if(arr[j]<arr[k])j=i,l=k-i+1,num[k]=i;
                else num[k]=num[j]+l,j++;
                k++;
            }
            while(i<=j){
                i+=k-j;//这一步是寻找下一个lyndon串的起始位置,可以用计算式直接计算出来,这个循环等价于i+=(k-i)/(k-j)*(k-j);
            }
        }
        ll ans=0,tmp=1;
        for(int i=1;i<=len;i++){
            ans=ans+num[i]*tmp%mod;
            //printf("%lld ",num[i]);
            ans%=mod;
            tmp=tmp*1112%mod;
        }
        printf("%lld\n",ans);
    }
}

小记:这个题,在比赛的时候我钻牛角尖,想用后缀数组求,当时想的是用倍增法,对于每一个不能倍增,即第二个关键字为0的后缀,它的排名就等于它前面的最小后缀的起始位置,但是因为对于后缀数组的理解不够,也没写出来(其实写出来了大概率也会T)。而且还白白耽误了队友的时间,(因为我挺久没写后缀数组了,有点忘记了,所以叫队友写的板子,对不起队友一秒钟)。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值