字符串-Runs学习笔记
runs学习笔记
定义
Run
有一个三元组\(run=(l,r,p)\),其中\(l,r\)表示在字符串的\(s[l,r]\)区间,\(p\)表示在\(s[l,r]\)中字符串的字串的最小循环节
注意:
- 不存在扩展性,也就是说如果成立的话整个三元组会整体右移或者左移,或者向两边同时扩展
- \(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\):在\(<_0\)下\(a<b\),记作\(a<_0b\)
\(<_1\):在\(<_1\)下\(b<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,如果能够将范围连起来就找到一个
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))\)
结尾
写的太干涩了,我估计不会看第二遍这个东西,自己边看边写都要写吐了