字符串-Runs学习笔记

发布时间 2023-04-30 18:51:04作者: 6penny

runs学习笔记

学习链接

定义

Run

有一个三元组run=(l,r,p),其中l,r表示在字符串的s[l,r]区间,p表示在s[l,r]中字符串的字串的最小循环节

注意:

  1. 不存在扩展性,也就是说s[l-1]!=s[l+p-1],s[r+1]!=s[r-p+1],如果成立的话整个三元组会整体右移或者左移,或者向两边同时扩展

  2. 2p\leq r-l+1

其中run的指数(即循环串出现的次数)e_r=\frac{r-l+1}{p}

\rho_{run}(n)表示长度为n的字符串中其子串能够构成三元组Run的个数

\sigma_{run}(n)表示长度为n的字符串的子串构成的三元组的指数和最大 最大的原因是因为p的定义是最小循环节 ,即\sum_{k=1}^{t}\frac{r_k-l_k+1}{p_k},其中t为三元组个数(不就是\rho_{run}(n)吗)

Lyndon根

在三元组run=(l,r,p)表示的区间里面,长度为p的Lyndon串

run的所有循环节中最小表示的的那一个

一个Run里面最少存在一个Lyndon根,证明显然

run=(l,r,p)的lyndon根s[u,u+p-1]满足u!=l,那么其为真Lyndon根

重载大小运算

<_0:在<_0a<b,记作a<_0b

<_1:在<_1b<a,记作b<_1a

其中a,b均为字符

Lyndon数组

l_t[i]表示在<_t的情况下,左端点为i的最长Lyndon串的右端点

性质

性质1:周期相同的run的交长度小于p

证明:若大于p,在交集中可以选择一个循环节进行扩展,即为上述不存在扩展性

 

性质2:l_0[i]l_1[i]都存在一个i的值等于i,位置不重合

证明显然

 

性质3:对于一个三元组Run=(l,r,p),假设s[r+1]<_ts[r+1-p],那么任意Lyndon根s[u,u+p-1]都有l_t(u)=u+p-1

证明:对于一个s[u,v],u+p-1<v\leq j,满足其形式为w^k+w^`,对于v>j,不存在Lyndon串

 

性质4:s[u,l_0(u)],s[u,l_1(u)]不可能同时为两个run的Lyndon根

证明:设l_0(u)=u,那么有s[u]=s[u-1]+s[u+p-1]=s[l1(u)],故s[u,l_1(u)]不是Lyndon串

 

性质5:任意两个不同的run的真Lyndon根的左端点集合不相交

证明见上

 

Runs定理

Runs定理如下:

\rho_{run}(n)<n,\sigma_{run}(n)<3n

证明:

设B(r)表示三元组Run的所有真Lyndon串的左端点集合,有任意B(r_1)\cap B(r_2),所以\sum_rB(r)\leq n-1,又因为B(r)\geq 1所以\rho_{run}(n)<n

r=(l,r,q)循环了x次,其真Lyndon根至少有x-1个,故|B(r)|>e_r-2,\sum_r(e_r-2)<=n-1,所以\sigma_{run}(n)<3n

 

Runs的求解

算法1

枚举周期p,然后在串中间打标记(每隔长度p打一次),大小为p的run一定会穿过两个点,相邻两个点之间求lcp,如果能够将范围连起来就找到一个

【集训队作业2018】串串划分

 struct STRUCT{
  ll sa[M],rk[M],hei[M],lim=125;
  ll num[M],x[M],y[M];
  ll lg[M],st[M][21];
  void init_sa(char *s){
  lim=125;
  for(ll i=1;i<=n;i++)num[x[i]=s[i]]++;
  for(ll i=2;i<=lim;i++)num[i]+=num[i-1];
  for(ll i=n;i>=1;i--)sa[num[x[i]]--]=i;
  for(ll k=1;k<=n;k<<=1){
  ll sum=0;
  for(ll i=n-k+1;i<=n;i++)y[++sum]=i;
  for(ll i=1;i<=n;i++)if(sa[i]>k)y[++sum]=sa[i]-k;
  for(ll i=1;i<=lim;i++)num[i]=0;
  for(ll i=1;i<=n;i++)num[x[i]]++;
  for(ll i=1;i<=lim;i++)num[i]+=num[i-1];
  for(ll i=n;i>=1;i--){
  sa[num[x[y[i]]]--]=y[i];
  y[i]=0;
  }
  swap(x,y);
  x[sa[1]]=sum=1;
  for(ll i=2;i<=n;i++){
  x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?sum:++sum;
  }
  if(sum==n)break;
  lim=sum;
  }
  for(ll i=1;i<=n;i++)rk[sa[i]]=i;
  for(ll i=1,k=0;i<=n;i++){
  if(rk[i]==1)continue;
  if(k)k--;
  ll pos=sa[rk[i]-1];
  while(i+k<=n&&pos+k<=n&&s[i+k]==s[pos+k])k++;
  hei[rk[i]]=k;
  }
  }
  void init_st(){
  for(ll i=2;i<=n;i++)lg[i]=lg[i>>1]+1;
  for(ll i=1;i<=n;i++)st[i][0]=hei[i]; 
for(ll j=1;j<=19;j++){ 
for(ll i=1;i+(1<<j)-1<=n;i++){ 
st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]); 



ll ask(ll l,ll r){ 
l=rk[l],r=rk[r]; 
if(l>r)swap(l,r); 
ll k=lg[r-l]; 
return min(st[l+1][k],st[r-(1<<k)+1][k]); 

}t1,t2; 
ll lcs(ll x,ll y){ 
return t2.ask(n-x+1,n-y+1); 

ll lcp(ll x,ll y){ 
return t1.ask(x,y); 

bool compare(ll x,ll y){ 
ll len=lcp(x,y); 
return s[x+len]<s[y+len]; 

void get_runs(ll fg){ 
for(ll i=n-1;i>=1;i--){ 
ly[i]=i+1; 
while(ly[i]&&compare(ly[i],i)==fg)ly[i]=ly[ly[i]]; 

for(ll len=1;len<=n;len++){ 
if(!ly[len])continue; 
ll x=len,y=ly[len],p=y-x; 
ll Lcs=lcs(x,y),Lcp=lcp(y,x); 
ll l=x-Lcs+1,r=y+Lcp-1; 
if(Lcs+Lcp>p&&st.insert(r*mod+l).second){//覆盖长度,满足条件 
for(ll i=l-1;i<l+2*p-1&&i+2*p<=r;i++){ 
len_run[++cnt]=p; 
for(ll j=i+2*p;j<=r;j+=2*p)id[j].push_back(cnt); 



}

时间复杂度:n(log(n))

算法2

由于run的Lyndon根一定是形如s[i,l_t(i)],所以先求出Lyndon数组在进行扩展,求 Lyndon 数组可以考虑一种从后向前构造 Lyndon 分解的方法,即从后往前扫,维护一个单调栈,每次加入一个字符,检查能不能和栈顶的串合并(我不会写)

时间复杂度:n(log(n))

结尾

写的太干涩了,我估计不会看第二遍这个东西,自己边看边写都要写吐了