题意:给定字符串,求所有前缀的最小后缀。
n≤2×107n\leq 2\times10^7n≤2×107
最小后缀就是Lyndon分解的最后一段。而Duval本质上是可以重复修改的增量算法,所以是可以做的。
记ansians_iansi为前缀iii的最小后缀。设维护未确定的循环节的指针为i,j,ki,j,ki,j,k,即Si...k−1=t+t+...+t+t1S_{i...k-1}=t+t+...+t+t_1Si...k−1=t+t+...+t+t1,其中ttt为Lyndon Word,t1t_1t1为其可空前缀,j=k−∣t∣j=k-|t|j=k−∣t∣
当Sj=SkS_j=S_kSj=Sk时,根据意识流,前缀jjj的最小后缀一定在[i,j][i,j][i,j]内。因为是个循环,所以ansk=ansj+k−jans_k=ans_j+k-jansk=ansj+k−j
当Sj<SkS_j<S_kSj<Sk,令j=ij=ij=i,此时Sj...kS_{j...k}Sj...k是个Lyndon Word,所以ansk=jans_k=jansk=j
当Sj>SkS_j>S_kSj>Sk,相当于t1t_1t1这一段的ansansans都是假的,需要重新计算,不用管。但∣t1∣=1|t_1|=1∣t1∣=1的时候会出一些奇怪的问题,需要把anskans_kansk的值算出来,为iii或kkk。
复杂度O(n)O(n)O(n)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#define MAXN 20000005
using namespace std;
char s[MAXN];
int ans[MAXN];
const int MOD=1e9+7;
int main()
{int T;scanf("%d",&T);while (T--){scanf("%s",s+1);int n=strlen(s+1);for (int i=1;i<=n;i++) ans[i]=0;ans[1]=1;for (int i=1;i<=n;){int j=i,k=i+1;ans[k]=k;while (s[j]<=s[k]){if (s[j]==s[k]) ans[k]=ans[j]+k-j,++j;else ans[k]=j=i;++k;}while (i<=j) i+=k-j;ans[k]=i;}
// for (int i=1;i<=n;i++) printf("%d%c",ans[i]," \n"[i==n]);int sum=0;for (int i=1,x=1;i<=n;i++,x=x*1112ll%MOD) sum=(sum+1ll*ans[i]*x)%MOD;printf("%d\n",sum);}return 0;
}