字符串杂项
本篇博客还未完成。后续还有一点内容。
一些记号约定
表示字符串 截取下标在 中的字符得到的子串。 表示两个字符(串) 连接的结果。 表示将 重复 遍的结果。 表示 的反串。- 下文若无特殊说明,字符串下标从
开始。
最小表示法
对于字符串
下面我们来求长度为
我们设置
,则 。所以开始下标在 这个区间一定没有 优。令 。 ,同理 。
存在一个优化:移动
最后取
时间复杂度
下面的代码可以通过模板题 P1368 【模板】最小表示法。
#include<bits/stdc++.h>
using namespace std;
const int N=300010;
int a[N];
int main(){
int n,x=0,y=1,k=0,ans;
scanf("%d",&n);
for(int i=0;i<=n-1;i++){
scanf("%d",&a[i]);
}
while(x<=n-1&&y<=n-1&&k<=n-1){
if(a[(x+k)%n]==a[(y+k)%n]){
k++;
}
else{
if(a[(x+k)%n]>a[(y+k)%n]){
x=max(x+k+1,y+1);
}
else{
y=max(y+k+1,x+1);
}
k=0;
}
}
ans=min(x,y);
for(int i=0;i<=n-1;i++){
printf("%d ",a[(ans+i)%n]);
}
}
下面有几个板子:
Lyndon 分解
定义
对于字符串
对于字符串
对于字符串
性质
性质 1:对于两个 Lyndon 串
性质 1 证明:
性质 2:每个字符串都有 Lyndon 分解。
性质 2 证明:我们先将
性质 3:每个字符串的 Lyndon 分解是唯一的。
性质 3 证明:设字符串
性质 4:现有长度为
性质 4 证明:因为
得证。
性质 5:Lyndon 串没有 border。
性质 5 证明:如果存在 border,那么 border 就是最小后缀,这个串就不是 Lyndon 串。
求解
我们使用 Duval 算法求出一个字符串
,不会改变近似 Lyndon 串的性质, 。 ,通过性质 3 可以得出 为 Lyndon 串。并且 。因此根据性质 1,我们可以一直向前合并,把 作为一个新的 Lyndon 串。即 。 ,再往后不可能形成新的 Lyndon 串。一直令 ,把前面的循环的 都拎出来。
时间复杂度
下面的代码可以通过模板题 P6114 【模板】Lyndon 分解。
#include<bits/stdc++.h>
using namespace std;
const int N=5000010;
char a[N];
int main(){
int n,x=1,y,z,ans=0;
scanf("%s",a+1);
n=strlen(a+1);
while(x<=n){
y=x;
z=x+1;
while(a[y]<=a[z]&&z<=n){
if(a[y]==a[z]){
y++;
z++;
}
else{
y=x;
z++;
}
}
while(x<=y){
x+=(z-y);
ans^=x-1;
}
}
printf("%d",ans);
}
Lyndon 分解与最小表示法
我们将长为
例题
先来一个小练习。
CF100162G Lyndon Words
给出
考虑直接搜索。按照 Duval 算法的流程,记录
#include<bits/stdc++.h>
using namespace std;
int n,m,l,r,now;
char s[50];
void dfs(int y,int z){
if(y==1){
now++;
if(now>=l&&now<=r){
printf("%s\n",s+1);
}
if(now==r){
return;
}
}
if(z>n){
return;
}
for(int i=1;i<=m&&now<r;i++){
s[z]='a'+i-1;
if(s[y]==s[z]){
dfs(y+1,z+1);
}
else if(s[y]<s[z]){
dfs(1,z+1);
}
}
s[z]=0;
}
int main(){
int cnt=0;
while(scanf("%d%d%d%d",&n,&m,&l,&r)!=EOF){
now=0;
printf("Case %d:\n",++cnt);
for(int i=1;i<=m&&now<r;i++){
s[1]='a'+i-1;
dfs(1,2);
}
}
}
P5334 [JSOI2019] 节日庆典
给出长为
考虑魔改 Duval 算法。还是有三个指针
-
, 形成一个新的 Lyndon 串。 。 -
,把前面的 Lyndon 串拎出来,后面的一段答案还不能确定。 -
,串为 。考虑答案可能是什么样的。一是 。二是下标在 中。对于后者,我们考虑出来的最小表示法最后一定有循环节,去掉一个就是 的最小表示法,所以为 (当然,需要 )。我们比较这两个的大小。即比较 与 。注意到 一定是 加上若干个 Lyndon 串的周期得到的,所以 。然后我们就只要比较 与 。分成 段比较,注意到两段都是一个前缀和一个子串比较,使用 exKMP 预处理出 数组即可。
时间复杂度
#include<bits/stdc++.h>
using namespace std;
const int N=3000010;
int z[N],ans[N];
char s[N];
void init(int n){
int l=0,r=0;
for(int i=2;i<=n;i++){
if(i<r){
z[i]=min(r-i+1,z[i-l+1]);
}
while(i+z[i]<=n&&s[z[i]+1]==s[i+z[i]]){
z[i]++;
}
if(i+z[i]-1>r){
l=i;
r=i+z[i]-1;
}
}
}
int getmin(int i,int j,int k,int ans){
int x=i+k-(ans+k-j)+1;
if(z[x]<k-x+1){
return s[x+z[x]]<s[z[x]+1]?i:ans+k-j;
}
x=k-x+2;
if(z[x]<i-1){
return s[x+z[x]]<s[z[x]+1]?ans+k-j:i;
}
else{
return i;
}
}
int main(){
int n,x=1,y,z;
scanf("%s",s+1);
n=strlen(s+1);
init(n);
while(x<=n){
if(!ans[x]){
ans[x]=x; //此处应为 Lyndon 串开头。
}
y=x;
z=x+1;
while(s[y]<=s[z]){
if(s[y]==s[z]){
if(!ans[z]){
if(ans[y]>=x){
ans[z]=getmin(x,y,z,ans[y]);
}
else{
ans[z]=x;
}
}
y++;
z++;
}
else{
y=x;
if(!ans[z]){
ans[z]=x;
}
z++;
}
}
while(x<=y){
x+=(z-y);
}
}
for(int i=1;i<=n;i++){
printf("%d ",ans[i]);
}
}
P9719 [EC Final 2022] Minimum Suffix
对于长度为
你需要从
考虑我们能从
考虑模拟 Duval 算法。我们对于每一个 Lyndon 串
-
,判断方法为 。 -
,形成了新的 Lyndon 串。判断方法为 。 -
,因为我们最先已经分出了 Lyndon 串,这种情况不会出现,如果不是前面 种情况即无解。
于是我们可以对于每个
构造考虑贪心。对于同一个 Lyndon 串,从前往后考虑。若
当有多个 Lyndon 串时,因为前面的不小于后面的,因此从后往前贪心。记后一个 Lyndon 串为
-
,直接 即可。 -
,与前面的有相同关系,因此需把前一个 的 加上 。然后从 重新开始贪心。此时发现 已经大于 ,因此这种调整最多只会进行一次。
如果最后
时间复杂度
#include<bits/stdc++.h>
using namespace std;
const int N=3000010;
int m,a[N],last[N],pre[N],p[N],val[N],s[N];
int las(int x){
if(x>m){
return 0;
}
return last[x];
}
int main(){
int t,n,l,r,len,gt,now;
scanf("%d",&t);
while(t--){
m=0;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&p[i]);
}
r=n;
while(r>=1){
l=p[r];
val[l]=1;
for(int i=l;i<=r;i++){
if(p[i]<l){
printf("-1\n");
goto lass;
}
}
len=1;
for(int i=l+1;i<=r;i++){
pre[i]=i-len;
if(p[i]==l){
val[i]=1;
len=i-l+1;
}
else if(i-p[i]==i-len-p[i-len]){
val[i]=0;
}
else{
printf("-1\n");
goto lass;
}
}
gt=0; //是否大于
s[l]=las(1);
for(int i=l+1;i<=r;i++){
s[i]=s[pre[i]]+val[i];
if(s[i]>las(i-l+1)){
gt=1;
}
if(!gt&&s[i]<las(i-l+1)){
if(val[i]){
s[i]=las(i-l+1);
}
else{
now=i;
while(val[now]!=1){
now--;
}
i=now;
s[i]++;
gt=1;
}
}
}
if(!gt&&r-l+1<m){
now=r;
while(val[now]!=1){
now--;
}
s[now]++;
for(int i=now+1;i<=r;i++){
s[i]=s[pre[i]]+val[i];
}
}
m=r-l+1;
for(int i=l;i<=r;i++){
last[i-l+1]=s[i];
}
r=l-1;
}
for(int i=1;i<=n;i++){
printf("%d ",s[i]+1);
}
printf("\n");
lass:;
}
}
CF594E Cutting the Line
给定长为
大分讨警告。
下文记
若
否则我们先考虑
注意到我们可以得到一种方案,使得每个被划分出来的子串都被翻转过。把答案串中未翻转的子串视为单个字符即可。题意转化为:每次取
考虑
- 若划分出的串相邻且相同,则可以把它们合并。
- 若划分出的串相邻且均为回文串,则可以把他们合并,最后拿出来的时候翻转,相当于在
中不翻转。
考虑一个引理:一个 Lyndon 串为回文串当且仅当它只包含
证明:考虑 Lyndon 串性质 5,若长度大于
当
-
若
,则将 个串直接拿出来, 减去 。 -
否则,若前面一种 Lyndon 串的长度
,同样将 个串拿出来, 减去 。 -
否则,可以将这种串与前面的合并,
不变。
最后来考虑
相当于要把现在的
记
因此,我们找到最大的一个
暴力比较一次
总时间复杂度
#include<bits/stdc++.h>
using namespace std;
const int N=5000010;
int n,cnt,z[N<<1];
char s[N],w[N],ans[N],tmp[N],q[N<<1];
struct ss{
int l,r,len;
}p[N];
bool check1(char *a,char *b){
for(int i=1;i<=n;i++){
if(a[i]<b[i]){
return 0;
}
else if(a[i]>b[i]){
return 1;
}
}
return 0;
}
void Lyndon(){
int x=1,y,z;
while(x<=n){
y=x;
z=x+1;
while(w[y]<=w[z]){
if(w[y]==w[z]){
y++;
z++;
}
else{
y=x;
z++;
}
}
p[++cnt]={x,0,z-y};
while(x<=y){
x+=(z-y);
}
p[cnt].r=x-1;
}
}
void upd(char *now){
for(int i=1;i<=n;i++){
if(now[i]<ans[i]){
for(int j=1;j<=n;j++){
ans[j]=now[j];
}
return;
}
else if(now[i]>ans[i]){
return;
}
}
}
void get_min(char *minn){
int x=1,y=2,k=0,st;
while(x<=n&&y<=n&&k<=n-1){
if(w[(x+k-1)%n+1]==w[(y+k-1)%n+1]){
k++;
}
else{
if(w[(x+k-1)%n+1]>w[(y+k-1)%n+1]){
x=max(x+k+1,y+1);
}
else{
y=max(y+k+1,x+1);
}
k=0;
}
}
st=min(x,y);
for(int i=0;i<=n-1;i++){
minn[i+1]=w[(st+i-1)%n+1];
}
}
void exkmp(){
int l=0,r=0;
for(int i=1;i<=n;i++){
q[i]=w[i];
}
for(int i=n+1;i<=n*2;i++){
q[i]=w[n-(i-n)+1];
}
for(int i=2;i<=n*2;i++){
if(r>i){
z[i]=min(r-i+1,z[i-l+1]);
}
while(i+z[i]<=n*2&&q[z[i]+1]==q[i+z[i]]){
z[i]++;
}
if(i+z[i]-1>r){
l=i;
r=i+z[i]-1;
}
}
}
bool cmp(int i,int j){
int x=n+(n-(j-1))+1;
if(z[x]<(j-1)-i+1){
return w[z[x]+1]<w[j-1-z[x]]?1:0;
}
x=(j-1)-i+2;
if(z[x]<i-1){
return w[z[x]+1]<w[x+z[x]]?0:1;
}
else{
return 0;
}
}
void get_kmp(char *minn){
int now=1,cnt=0;
for(int i=2;i<=n;i++){
if(cmp(now,i)){
now=i;
}
}
for(int i=n;i>=now;i--){
minn[++cnt]=w[i];
}
for(int i=1;i<=now-1;i++){
minn[++cnt]=w[i];
}
}
void get_min2(char *minn){
int now=cnt,fl=1,ans;
while(now>1){
for(int i=p[now].l,j=p[now-1].l;i<=p[now].r;i++,j++){
if(w[i]<w[j]){
fl=0;
}
}
if(!fl){
break;
}
now--;
}
ans=now;
while(now<cnt){
now++;
for(int i=p[now].l-1,j=p[now-1].l+p[now].len;j<=n;i--,j++){
if(w[i]<w[j]){
ans=now;
break;
}
else if(w[i]>w[j]){
break;
}
}
}
ans=p[ans].l;
now=0;
for(int i=ans;i<=n;i++){
minn[++now]=w[i];
}
for(int i=ans-1;i>=1;i--){
minn[++now]=w[i];
}
}
int main(){
int k;
scanf("%s%d",s+1,&k);
n=strlen(s+1);
for(int i=1;i<=n;i++){
w[i]=s[n-i+1];
}
if(k==1){
printf("%s",check1(s,w)==0?(s+1):(w+1));
return 0;
}
Lyndon();
while(cnt&&k>=3){
for(int i=p[cnt].l;i<=p[cnt].r;i++){
printf("%c",w[i]);
}
if(p[cnt].len!=1){
k--;
}
else{
if(p[cnt-1].len!=1){
k--;
}
}
cnt--;
}
if(!cnt){
return 0;
}
n=p[cnt].r;
for(int i=1;i<=n;i++){
ans[i]=w[n-i+1];
}
get_min(tmp);
upd(tmp);
exkmp();
get_kmp(tmp);
upd(tmp);
get_min2(tmp);
upd(tmp);
printf("%s",ans+1);
}
Yandex.Algorithm.2015 Round 2.2 F Lexicographically Smallest String
给你一个字符串
显然最后字符串的开始字符一定是字符串内最小的。删去已经合法的前缀,转化为需要翻转一个前缀,即上一道题最后一种情况。代码不放了。
QOJ #243 Lyndon Substring / HDU 6306 Lyndon Substring
给出
考虑先把
确定了左端点,我们来考虑确定右端点。我们只需在
对于字符串
代码中字符串下标从
#include<bits/stdc++.h>
using namespace std;
const int N=100010,base=41,mod=1e9+9;
string s[N];
int maxx[N],pw[N];
vector<int>las[N];
vector<pair<int,int> >lyndon[N];
struct hs{
vector<int>p;
void set(string& s){
int n=s.length();
p.resize(n);
p[0]=s[0];
for(int i=1;i<=n-1;i++){
p[i]=(1ll*p[i-1]*base+s[i])%mod;
}
}
int get(int l,int r){
if(l==0){
return p[r];
}
return (p[r]-1ll*p[l-1]*pw[r-l+1]%mod+mod)%mod;
}
}p[N];
void Lyndon(int i,string& s){
int n=s.length(),x=0,y,z;
maxx[i]=0;
las[i].clear();
lyndon[i].clear();
while(x<=n-1){
y=x;
z=x+1;
while(s[y]<=s[z]){
if(s[y]==s[z]){
y++;
z++;
}
else{
y=x;
z++;
}
}
if(z==n){
las[i].push_back(x);
}
while(x<=y){
lyndon[i].push_back({x,x+z-y-1});
x+=(z-y);
}
maxx[i]=max(maxx[i],z-y);
}
las[i].push_back(n);
}
int calc(int x,int y,int a,int len){
if(a+len-1<s[x].length()){
return p[x].get(a,a+len-1);
}
if(a>=s[x].length()){
return p[y].get(a-s[x].length(),a-s[x].length()+len-1);
}
return (1ll*p[x].get(a,s[x].length()-1)*pw[a+len-s[x].length()]%mod+p[y].get(0,a-s[x].length()+len-1))%mod;
}
char get(int x,int y,int a){
if(a<s[x].length()){
return s[x][a];
}
return s[y][a-s[x].length()];
}
bool check(int x,int y,int a,int b,int len){
int l=1,r=len,mid,ans=0;
if(calc(x,y,a,len)==calc(x,y,b,len)){
return 0;
}
while(l<=r){
mid=(l+r)>>1;
if(calc(x,y,a,mid)==calc(x,y,b,mid)){
l=mid+1;
ans=mid;
}
else{
r=mid-1;
}
}
return get(x,y,a+ans)<get(x,y,b+ans);
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t,n,m,x,y,a,b,now,ans,l,r,mid,anss;
cin>>t;
pw[0]=1;
for(int i=1;i<=100000;i++){
pw[i]=1ll*pw[i-1]*base%mod;
}
while(t--){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>s[i];
Lyndon(i,s[i]);
p[i].set(s[i]);
}
while(m--){
cin>>x>>y;
now=-1;
ans=max(maxx[x],maxx[y]);
for(int i=0;i<las[x].size()-1;i++){
a=las[x][i];
b=las[x][i+1];
if(check(x,y,a,b,s[x].length()-b+s[y].length())){
now=a;
break;
}
}
if(now!=-1){
l=0;
r=lyndon[y].size()-1;
while(l<=r){
mid=(l+r)>>1;
if(check(x,y,now,s[x].length()+lyndon[y][mid].first,lyndon[y][mid].second-lyndon[y][mid].first+1)){
l=mid+1;
anss=lyndon[y][mid].second+1;
}
else{
r=mid-1;
}
}
ans=max(ans,(int)s[x].length()-now+anss);
}
cout<<ans<<"\n";
}
}
}
事实上,这里的
Significant Suffixes
定义
一个字符串
性质
性质 1:对于
性质 1 证明:若条件不成立,
注意到此时
性质 2:对于
性质 2 证明:若存在
性质 2 推论:串
例题
P5211 [ZJOI2017] 字符串
维护一个动态字符串
-
输入
,对于所有 ,将 修改为 。 -
输入
,输出子串 的字典序最小的后缀的起点位置。
DS 警告。
我们考虑使用线段树维护这个字符串的 Significant Suffixes。考虑线段树上一个节点的两个子节点
查询字符串大小关系时,因为带修,并且查询次数多,考虑分
#include<bits/stdc++.h>
using namespace std;
const int N=200010,M=450,base=41,mod=1e9+9,maxx=2e8;
int n,a[N],ans;
vector<int>v[N<<2];
struct Block{
int blo,cnt,bel[N],L[M],R[M],pw[N],p[N],q[N],del[M],sumpw[N];
void init(){
blo=sqrt(n);
cnt=ceil(1.0*n/blo);
pw[0]=sumpw[0]=1;
for(int i=1;i<=n;i++){
pw[i]=1ll*pw[i-1]*base%mod;
sumpw[i]=(sumpw[i-1]+pw[i])%mod;
}
for(int i=1;i<=cnt;i++){
L[i]=(i-1)*blo+1;
R[i]=min(i*blo,n);
p[L[i]]=a[L[i]];
bel[L[i]]=i;
for(int j=L[i]+1;j<=R[i];j++){
p[j]=(1ll*p[j-1]*base+a[j])%mod;
bel[j]=i;
}
}
for(int i=1;i<=cnt;i++){
q[i]=(1ll*q[i-1]*pw[R[i]-L[i]+1]+p[R[i]])%mod;
}
}
void update(int x,int y,int z){
int ql=bel[x],qr=bel[y];
if(ql==qr){
for(int i=x;i<=y;i++){
a[i]+=z;
}
p[L[ql]]=a[L[ql]];
for(int i=L[ql]+1;i<=R[ql];i++){
p[i]=(1ll*p[i-1]*base+a[i])%mod;
}
for(int i=ql;i<=cnt;i++){
q[i]=(1ll*q[i-1]*pw[R[i]-L[i]+1]+(p[R[i]]+1ll*sumpw[R[i]-L[i]]*del[i])%mod)%mod;
}
return;
}
for(int i=x;i<=R[ql];i++){
a[i]+=z;
}
p[L[ql]]=a[L[ql]];
for(int i=L[ql]+1;i<=R[ql];i++){
p[i]=(1ll*p[i-1]*base+a[i])%mod;
}
for(int i=ql+1;i<=qr-1;i++){
del[i]+=z;
}
for(int i=L[qr];i<=y;i++){
a[i]+=z;
}
p[L[qr]]=a[L[qr]];
for(int i=L[qr]+1;i<=R[qr];i++){
p[i]=(1ll*p[i-1]*base+a[i])%mod;
}
for(int i=ql;i<=cnt;i++){
q[i]=(1ll*q[i-1]*pw[R[i]-L[i]+1]+(p[R[i]]+1ll*sumpw[R[i]-L[i]]*del[i])%mod)%mod;
}
}
int get(int x){
int qx=bel[x];
return (1ll*q[qx-1]*pw[x-L[qx]+1]+p[x]+1ll*sumpw[x-L[qx]]*del[qx])%mod;
}
int get(int x,int y){
return (get(y)-1ll*get(x-1)*pw[y-x+1]%mod+mod)%mod;
}
}d;
int cmp(int x,int y,int z,int op){
int l=1,r=z-max(x,y)+1,mid,now=0;
while(l<=r){
mid=(l+r)>>1;
if(d.get(x,x+mid-1)==d.get(y,y+mid-1)){
now=mid;
l=mid+1;
}
else{
r=mid-1;
}
}
if(now==z-max(x,y)+1){
return op;
}
return d.get(x+now,x+now)<d.get(y+now,y+now);
}
void pushup(int rt,int r){
int now=0;
v[rt]=v[rt<<1|1];
for(auto i:v[rt<<1]){
if(!now||cmp(i,now,r,1)){
now=i;
}
}
v[rt].push_back(now);
}
void build(int l,int r,int rt){
int mid=(l+r)>>1;
if(l==r){
v[rt].push_back(l);
return;
}
build(l,mid,rt<<1);
build(mid+1,r,rt<<1|1);
pushup(rt,r);
}
void update(int x,int y,int z,int l,int r,int rt){
int mid=(l+r)>>1;
if(x<=l&&y>=r){
return;
}
if(x<=mid){
update(x,y,z,l,mid,rt<<1);
}
if(y>=mid+1){
update(x,y,z,mid+1,r,rt<<1|1);
}
pushup(rt,r);
}
void query(int x,int y,int l,int r,int rt){
int mid=(l+r)>>1;
if(x<=l&&y>=r){
for(auto i:v[rt]){
if(!ans||cmp(i,ans,y,0)){
ans=i;
}
}
return;
}
if(y>=mid+1){
query(x,y,mid+1,r,rt<<1|1);
}
if(x<=mid){
query(x,y,l,mid,rt<<1);
}
}
int main(){
int m,op,x,y;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
a[i]+=maxx;
}
d.init();
build(1,n,1);
while(m--){
scanf("%d%d%d",&op,&x,&y);
if(op==1){
scanf("%d",&op);
d.update(x,y,op);
update(x,y,op,1,n,1);
}
else{
ans=0;
query(x,y,1,n,1);
printf("%d\n",ans);
}
}
}
Lyndon 数组
定义
一个字符串
求解
我们从后往前扫
例题
QOJ #7406 Longest Lyndon Prefix
板子题,求
#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int N=100010;
const ull base=31;
char s[N];
int p[N],ans[N];
ull h[N],pw[N];
ull get(int l,int r){
return h[r]-h[l-1]*pw[r-l+1];
}
int cmp(int a,int b,int c,int d){
int l=1,r=min(b-a+1,d-c+1),mid,ans=0;
while(l<=r){
mid=(l+r)>>1;
if(get(a,a+mid-1)==get(c,c+mid-1)){
ans=mid;
l=mid+1;
}
else{
r=mid-1;
}
}
if(ans==min(b-a+1,d-c+1)){
return (ans==d-c+1)?0:1;
}
return s[a+ans]<s[c+ans];
}
int main(){
int t,n;
scanf("%d",&t);
pw[0]=1;
while(t--){
scanf("%d%s",&n,s+1);
for(int i=1;i<=n;i++){
pw[i]=pw[i-1]*base;
h[i]=h[i-1]*base+s[i];
}
for(int i=n;i>=1;i--){
p[i]=i+1;
while(p[i]<=n&&cmp(i,p[i]-1,p[i],p[p[i]]-1)){
p[i]=p[p[i]];
}
ans[i]=p[i]-i;
}
for(int i=1;i<=n;i++){
printf("%d ",ans[i]);
}
printf("\n");
}
}
Runs
定义
定义长为
-
存在长度为 的最小周期,且周期至少完整出现 次。 -
不可向两段扩展,即 是极长的。
求解
显然,一个 run 有
注意到对于
事实上,runs 的个数
时间复杂度
例题
P6656 【模板】Runs
板子。
#include<bits/stdc++.h>
using namespace std;
const int N=1000010,base=41,mod=1e9+9;
int n,p[N],h[N],pw[N];
char s[N];
struct runs{
int l,r,len;
bool operator<(runs b){
if(l!=b.l){
return l<b.l;
}
return r<b.r;
}
bool operator==(runs b){
return l==b.l&&r==b.r&&len==b.len;
}
};
vector<runs>ans;
void init(){
pw[0]=1;
for(int i=1;i<=n;i++){
h[i]=(1ll*h[i-1]*base+s[i])%mod;
pw[i]=1ll*pw[i-1]*base%mod;
}
}
int get(int l,int r){
return (h[r]-1ll*h[l-1]*pw[r-l+1]%mod+mod)%mod;
}
int lcs(int x,int y){
int l=1,r=min(x,y),mid,ans=0;
if(s[x]!=s[y]){
return 0;
}
while(l<=r){
mid=(l+r)>>1;
if(get(x-mid+1,x)==get(y-mid+1,y)){
l=mid+1;
ans=mid;
}
else{
r=mid-1;
}
}
return ans;
}
int lcp(int x,int y){
int l=1,r=n-max(x,y)+1,mid,ans=0;
if(s[x]!=s[y]){
return 0;
}
while(l<=r){
mid=(l+r)>>1;
if(get(x,x+mid-1)==get(y,y+mid-1)){
l=mid+1;
ans=mid;
}
else{
r=mid-1;
}
}
return ans;
}
int cmp(int a,int b,int c,int d){
int l=1,r=min(b-a+1,d-c+1),mid,ans=0;
while(l<=r){
mid=(l+r)>>1;
if(get(a,a+mid-1)==get(c,c+mid-1)){
ans=mid;
l=mid+1;
}
else{
r=mid-1;
}
}
if(ans==min(b-a+1,d-c+1)){
return (ans==d-c+1)?0:1;
}
return s[a+ans]<s[c+ans];
}
void add(int x,int y){
int l=lcs(x-1,y-1),r=lcp(x,y);
if((y+r-1)-(x-l)+1>=2*(y-x)){
ans.push_back({x-l,y+r-1,y-x});
}
}
int main(){
scanf("%s",s+1);
n=strlen(s+1);
init();
for(int i=n;i>=1;i--){
p[i]=i+1;
while(p[i]<=n&&cmp(i,p[i]-1,p[i],p[p[i]]-1)){
p[i]=p[p[i]];
}
add(i,p[i]);
}
for(int i=1;i<=n;i++){
s[i]=25-(s[i]-'a')+'a';
}
init();
for(int i=n;i>=1;i--){
p[i]=i+1;
while(p[i]<=n&&cmp(i,p[i]-1,p[i],p[p[i]]-1)){
p[i]=p[p[i]];
}
add(i,p[i]);
}
sort(ans.begin(),ans.end());
ans.erase(unique(ans.begin(),ans.end()),ans.end());
printf("%d\n",ans.size());
for(auto i:ans){
printf("%d %d %d\n",i.l,i.r,i.len);
}
}
参考文献
[1]. https://zhuanlan.zhihu.com/p/664357638
[2]. https://www.cnblogs.com/ptno/p/16418308.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)