Lyndon Word&Runs 学习笔记

参考资料

0. 一些记号

一些通用的记号

  • 字符串下标从 \(1\) 开始。用 \(s_i\) 表示 \(s\) 的第 \(i\) 个字符,\(|s|\) 表示字符串 \(s\) 的总长度。
  • \(\Sigma\) 表示字符集大小,\(\Sigma^*\) 表示定义在 \(\Sigma\) 上的字符串。\(\epsilon\) 表示空串。\(\$\) 表示特殊字符,且 \(\forall c\in\Sigma\ ,\ \$ >c\)
  • \(\overline\Sigma=\Sigma\cup\{\$\}\)\(\overline{\Sigma^*}\) 表示定义在 \(\overline{\Sigma}\) 上的特殊字符串。\(\Sigma^{+}\) 表示 \(\Sigma^*/\{\epsilon\}\),即去掉空串。
  • 默认 \(s_i\in\Sigma^*\),即普通字符串中不存在特殊字符。
  • \(st\)\(s+t\) 均表示将字符串 \(s\) 和字符串 \(t\) 按顺序拼接得到的字符串。
  • \(s^k\) 表示将 \(s\) 重复 \(k\) 遍得到的字符串。\(s^{\infty}\) 表示赋值无数遍得到的字符串。
  • \(s^{R}\) 表示将 \(s\) 翻转后得到的字符串。
  • \(s[i:j]\) 表示由区间 \([i,j]\) 中的字符构成的子串,若 \(l>r,\ s[l:r]=\epsilon\)\(s[1:i]\) 简写为 \(s[:i]\)\(s[i:|s|]\) 简写为 \(s[i:]\)
  • \(s<t\) 表示比较 \(s\)\(t\) 的字典序。
  • \(<^{\$}\):绝对小于,\(u<^{\$}\) 等价于 \(u+\$<v\)\(>^{\$}\) 等价于 \(u>v+\$\)

一些定义

  • \(\operatorname{lcp}(s,t)\)\(s\)\(t\) 的最长公共前缀。
  • \(\text{period}\):整数 \(l\)\(s\) 的一个 \(\text{period}\),当且仅当 \(\forall i\in[1,|s-l|],s_{i}=s_{i+l}\)
  • \(\text{border}\):整数 \(l\)\(s\) 的一个 \(\text{border}\),当且仅当 \(\forall i\in[1,l],s_{i}=s_{n-l+1}\)
  • 整循环串:\(s\) 是一个整循环串当且仅当 \(\exists \ t\in\Sigma^*\ ,\ s=t^k\),其中 \(k\) 是任意整数。
  • 循环串:\(s\) 是一个循环串当且仅当 \(\exists \ t\in\Sigma^*,\ s=t^k+t[:p]\),其中 \(k\) 是任意整数,\(p<|t|\)
  • 最短循环节:长度最小的 \(t\) 满足循环串 \(s\) 的定义式。

1. \(\text{Lyndon Word}\)

  • \(\textbf{Lyndon Word}\)\(s\) 是一个 \(\text{Lyndon Word}\) 当且仅当 \(s\) 比其所有后缀中字典序都小。即 \(\forall i>1,s[1:i]\)

比如 \(\texttt{ababb}\) 就是一个 \(\text{Lyndon Word}\)。而 \(\texttt{abab}\) 则不是,因为 \(\texttt{ab}<\texttt{abab}\)

1.1 一些基本性质

  • 性质 1:若 \(u<^{\$}v\),则 \(\forall w_1,w_2\in \Sigma^*\ ,\ u+w_1<v+w_2\)
  • 性质 2:若 \(s\) 是一个 \(\text{Lyndon Word}\)\(\forall i<|s|,s[1:i]<^{\$}s[n-i+1:n]\)

根据定义显然得证。

  • 性质 3:对于两个 \(\text{Lyndon Word}\ u,v\),若 \(u<v\),则 \(u+v\) 仍是一个 \(\text{Lyndon Word}\)

证明:令 \(w=u+v\)

任取 \(v\) 的一个后缀 \(v'=v[i:]\),若 \(|v'|\leq |u|\)\(u<^{\$}v\),则有 \(u<^{\$}v'\),故 \(w<v\)

否则有 \(u=v[1:|u|]\)。假设有 \(w>v\),对于 \(l=|v|-|u|\),有 \(v[:l]\geq v[n-l+1:]\),与性质 2 不符。

\(w\) 小于 \(v\) 中的任何一个后缀。

再任取 \(u\) 的一个后缀 \(u'=u[i:]\),由于 \(u>^{\$}u'\),故 \(w>u'+v\)

至此我们证明了 \(w\) 是一个 \(\text{Lyndon Word}\)

1.2 \(\text{Lyndon}\) 分解

  • \(\textbf{Lyndon}\) 分解:把一个字符串分解为若干个 \(\text{Lyndon Word}\),并且字典序单调不增。

形式化说,构造一个序列 \(t_i\),使 \(t_1+t_2+\cdots+t_n=s\),且 \(\forall i\ ,\ t_i\) 是一个 \(\text{Lyndon Word}\)

通常 \(\text{Lyndon}\) 分解采用 Duval 算法。

算法思路

考虑将字符依次加入,维护当前的 \(\text{Lyndon}\) 分解:

我们假设分解形式为 \(T+t'^k+t'[:p]\),其中 \(T=t_1+t_2+\cdots+t_{i}\)。保证有 \(t_1\geq t_2\geq \cdots \geq t_{i}>t'\)

考虑我们加入一个字符 \(c\),设 \(v\) 表示 \(t'[:p\bmod |t'|+1]\),如果:

  1. \(c=v\) :转移到分解 \(T+t'^k+t'[:p+1]\)(如果 \(p=|t'|\) 则是 \(T+t'^{k+1}\))。
  2. \(c<v\ ,\ t'[:p]+c<t'\) :令 \(j\in[1,k],s_{i+j}=t'\),转移到 \(T'+(t'[:p]+c)\)
  3. \(c>v\ ,\ t'^k+t'[:p]+c\) 是一个 \(\text{Lyndon Word}\) :令 \(s_{i+1}=t'^k+t'[:p]+c\),转移到 \(T'\)。,复杂度 \(O(n)\)

具体实现

考虑到 \(t'^k+t'[:p]\) 本质是一个循环串,故维护三个变量 \((i,j,k)\),其中 \(i\) 是首字母位置,\([j,k]\) 是最后一个循环节的位置。

这样对于情况一,令 \(i+1,j+1\)。情况二则从 \(i\) 开始每 \(k-j+1\) 划分一个分解。情况三则令 \(j=i\)

代码

#include<cstdio>
#include<algorithm>
#include<cstring>
#define N 5000010
using namespace std;
char s[N];
int p[N],t;
int main()
{
	scanf("%s",s+1);
	int n=strlen(s+1);
	for(int i=1,j=1,k=2;i<=n;j=i,k=i+1)
	{
		for(;k<=n && s[j]<=s[k];k++)
		{
			if(s[j]<s[k]) j=i;
			else j++;
		}
		for(;i<=j;i+=k-j) p[++t]=i+(k-j)-1;
	}
	int ans=0;
	for(int i=1;i<=t;i++) ans^=p[i];
	printf("%d\n",ans);
	return 0;
}

1.3 \(\text{Lyndon}\) 数组

  • \(\textbf{Lyndon}\) 数组:定义 \(\lambda_i\) 表示从 \(i\) 开始最长的 \(\text{Lyndon Word}\) 。即 \(s[i:\lambda_i]\) 是一个 \(\text{Lyndon Word}\) ,且 \(\forall j>\lambda_i\ ,\ s[i:j]\) 不是一个 \(\text{Lyndon Word}\)

一些性质

  • 性质1:若 \(u\) 是一个 \(\text{Lyndon Word}\)\(s\) 的一组 \(\text{Lyndon}\) 分解是 \(t_1+t_2+\cdots t_n\)。那么 \(u<t_1\) 当且仅当 \(u+s<s\)

证明:根据是否有 \(u<^{\$}t_1\) 分类讨论,如果没有再根据是否有 \(|u|<t_1\),如果是则递归 \(t_2\) 讨论,否则可以推出 \(t_1\) 不是一个 \(\text{Lyndon Word}\)

算法实现

从后往前加字符,考虑维护一个单调栈,设 \(t_0\) 是栈顶,满足 \(t_i\geq t_{i+1}\)。对于 \(p\) 位置新加的字符 \(c\),我们令 \(s'=c\)。每次判断是否有 \(s'<t_0\)。如果有则弹出栈顶,令 \(s_m=s_m+t_0\)。重复上述步骤直到满足单调性质,此时的 \(s_m\) 的右端点即 \(\lambda_p\)

使用Hash+二分处理两个串的大小关系可以做到 \(O(n\log n)\) 的复杂度。若使用 SA-IS 加 \(O(n)-O(1)\) ST表可以做到 \(O(n)\) 的时间复杂度。

2. \(\text{Runs}\)

  • \(\textbf{period}\): 定义一个整数 \(p\) 是一个字符串 \(s\)\(\text{period}\) ,当且仅当 \(\forall i\in[1,|s|-p],s_{i}=s_{i+p}\)

即某一个字符串不断循环得到 \(s\)。这里并不一定要整循环,比如 \(3\) 就是 \(\texttt{abbabba}\) 的一个 \(\text{period}\)

  • \(\textbf{run}\):定义三元组 \((i,j,p)\) 是一个 \(\text{run}\) ,当且仅当 \(s_{j+1}\neq s_{j-p+1},s_{i-1}\neq s_{i+p-1}\)\(p\)\([i,j]\) 最小的出现至少两次的循环节。

比如 \(\texttt{ababababba}\) 中:

\((1,8,2)\) 是一个 \(\text{run}\)

\((3,8,2)\) 不是,因为这个这个周期串可以继续向左延伸。

\((1,8,4)\) 不是,因为 \(p=4\) 不是 \(s[1:8]\) 的最小 \(\text{period}\)

\((6,10,3)\) 不是,因为 \(p=3\) 没有出现至少两次,即 \(p>\frac{10-6+1}{2}\)


一些定义

  • \(\textbf{Runs}\) :定义 \(\text{Runs}(s)\) 是字符串 \(s\) 的所有 \(\text{run}\) 的集合。

  • 指数:定义一个 \(\text{run}\) \((l,r,p)\) 的指数是 \(\frac {r-l+1} p\),即 \(\text{period}\) \(p\)\(s[l:r]\) 中的出现次数。记作 \(e_{(l,r,p)}\)

  • 定义两种偏序关系 \(<_0,<_1\)。由于只要满足 \(a<_0 b\rightarrow b<_1 a\),所以一般这里 \(<_0\) 表示“字典序较小”,\(<_1\) 表示“字典序较大”。一般我们认为空字符字典序最小,即 \(\varnothing<_0 \texttt{a}\ ,\ \texttt{a}<_0 \texttt{aa}\)

  • \(\ell\in\{0,1\}\) 表示一种偏序关系,\(\overline{\ell}\) 表示与 \(\ell\) 相反的偏序关系,以下 \(<_{\ell}\) 表示 \(\ell\) 对应的偏序关系

  • \(\textbf{Lyndon Root}\):定义 \(s[\lambda_l:\lambda_r]\)\((i,j,p)\)\(<\) 上的 \(\text{Lyndon Root}\),当且仅当 \([\lambda_l,\lambda_r]\subseteq [i,j]\)\(s[\lambda_l:\lambda_r]\) 是一个 \(\text{Lyndon Word}\)

  • \(B(r)\) 表示一个 \(\text{run}\) \(r\) 的所有除去开头\(\text{Lyndon Root}\) 的集合。即集合中的元素 \(s[i:j]\) 要求 \(i\neq r_l\)。显然有 \(|B(r)|\geq \lfloor e_r-1\rfloor\geq 1\)

  • \(\text{Beg}(B(r))\) 表示 \(B(r)\) 的所有起始端点组成的集合。

  • \(l_{\ell}(i)\) 表示所有从 \(i\) 开始的 \(\text{Lyndon Word}\) 中最长的那个。即 \(l_{\ell}(i)=[i,j],j=\max\{j'|s[i:j']\text{是一个 Lyndon Word}\}\)

  • \(\overline{s}=s+\$\),即末尾多一个特殊字符。

  • \(\text{run}\) \((i,j,p)\)\(\ell(\ell\in\{0,1\})\) 型的,当且仅当 \(s_{j-p+1}<_{\ell}s_{j+1}\)

一些推论

  • 引理1:对于字符串 \(s=t^kt[:p]\),其中 \(t\) 是一个 \(\text{Lyndon Word}\)\(t[:p]\) 表示 \(t\) 的一个前缀且在这里允许为空。现在在其后加入字符 \(a\)\(a\neq t_{p+1}\)。如果 \(a<t_{p+1}\) 那么 \(s=t^kt[:p]a\) 是任意前缀中最长的 \(\text{Lyndon Word}\)

这个其实就是上面 \(\text{Lyndon}\) 分解所用的结论。

  • 推论1:对于 \(\ell\)\(\text{run}\) \((i,j,p)\),其所有定义在 \(<_{\ell}\) 上的 \(\text{Lyndon Root}\) \(\lambda=[l_{\lambda},r_{\lambda}]\) 都有 \(\lambda=l_{\ell}(l_{\lambda})\),即其所有 \(\text{Lyndon Root}\) 都是相同起点中最长的 \(\text{Lyndon Root}\)

  • 推论2:对于任意 \(\text{run}\) \(r(i,j,p)\) ,其所有的 \(\text{Lyndon Root}\) 左端点不重复,即有 \(|\text{Beg}(B(r))|=|B(r)|\)

  • 引理2:对于任意位置 \(p\in[1,|s|]\),有且仅有一个 \(\ell\in\{0,1\}\) 满足 \(l_{\ell}(i)=\overline{s}[i:i]\)

证明:找到 \(\overline{s}[i:]\) 第一个与 \(\overline{s}_i\) 不同的位置 \(p'\)。即 \(\min\{p'|p'>i,\overline{s}_{p'}\neq \overline{s}_p\}\)。令 \(\ell\) 满足 \(s_{p'}<_{\ell}s_p\),由引理1显然有 \(l_{\ell}=[i,i]\)\(l_{\overline{\ell}}\neq[i,i]\)。(此处 WC2019 的课件貌似有误?课件好像取的是 \(\max\))。

  • 推论3\(\forall r,r\text{ is a run},r\neq r'\),有 \(\text{Beg}(B(r)) \cap \text{Beg}(B(r'))=\varnothing\)

具体证明大概就是根据引理 2 用反证推出如果存在交集,取交集的位置就能推出周期性,与 \(\text{run}\) 的定义不符。

  • 结论1:任意位置 \(p\) 最多存在于一个 \(Beg(B(r))\) 中。由一个 \(\text{Lyndon Root}\) 能唯一确定一个 \(\text{run}\) \(r\)。由于 \(\forall r,1\not\in \text{Beg}(B(r))\)\(\sum|\text{Beg}(B(r))|\leq n-1\),故有 \(|\text{Runs}(s)|<|s|\)

  • 结论2:由于 \(\text{Beg}(B(r))>e_r-1\)。由结论 1 可推得 \(\sum{e_r}\leq 3n-3\)


接下来考虑如何快速求的 \(\text{Runs}(s)\)

考虑我们先求出 \(l_{\ell}(i)\),即以 \(i\) 开头最长的 \(\text{Lyndon Word}\)。容易发现这个就是 1.3 \(\textbf{Lyndon}\) 数组

获得了所有 \(l_{\ell}(i)\) 后,我们令 \(p=l_{\ell}(i)-i+1\),观察其向左向右能延伸到的最远的地方 \([l',r']\)。对于三元组 \((l',r',p)\),我们只需要判断其是否满足 \(p\leq \frac{r'-l'+1} 2\) 即可。

实现细节:一种比较 \(\text{simple}\) 的方式是用 Hash+二分处理两个串的 \(\text{lcp}/\text{lcs}\)。对于子串大小比较直接比较 \(\text{lcp}\) 的后一位即可。判断延伸到的最远的位置 \([l',r']\),可以发现对于 \(l'\) 一定有 \(s[l':i]=s[l'+p-1:s+p-1]\),所以直接取 \(s[:i],s[:l_{\ell}(i)]\)\(\text{lcp}\) 即可。\(r'\) 同理。这样总复杂度是 \(O(n\log n)\)

上述复杂度瓶颈在于求 \(\text{lcp}\)。用 SA-IS 套 \(O(n)-O(1)\) ST表可以优化到 \(O(n)\)

至此就完成了 \(\text{Runs}\) 的求解。

LOJ173 Runs

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define N 1000010
#define ull unsigned long long
#define B 2333
using namespace std;
char str[N];
int s[N],n;
ull h[N],bs[N];
ull get(int l,int r){return h[r]-h[l-1]*bs[r-l+1];}
int lcp(int x,int y)
{
    int l=1,r=n-max(x,y)+1,res=0;
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(get(x,x+mid-1)==get(y,y+mid-1)) l=mid+1,res=mid;
        else r=mid-1;
    }
    return res;
}
int lcs(int x,int y)
{
    int l=1,r=min(x,y),res=0;
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(get(x-mid+1,x)==get(y-mid+1,y)) l=mid+1,res=mid;
        else r=mid-1;
    }
    return res;
}
bool cmp(int l1,int r1,int l2,int r2)//s[l1:r1]<s[l2:r2]
{
    int l=lcp(l1,l2);
    if(l>min(r1-l1,r2-l2)) return r1-l1<r2-l2;
    return s[l1+l]<s[l2+l];
}
struct runs{
    int i,j,p;
    runs(int i=0,int j=0,int p=0):i(i),j(j),p(p){}
    bool operator ==(const runs a)const{return i==a.i && j==a.j && p==a.p;}
    bool operator <(const runs a)const{return i==a.i?j<a.j:i<a.i;}
};
vector<runs>ans;
int st[N],t,run[N];
void lyndon()
{
    t=0;
    for(int i=n;i;i--)
    {
        st[++t]=i;
        for(;t>1 && cmp(i,st[t],st[t]+1,st[t-1]);t--);
        run[i]=st[t];
    }
}
void init()
{
    bs[0]=1;
    for(int i=1;i<=n;i++) h[i]=h[i-1]*B+s[i],bs[i]=bs[i-1]*B;
}
void get_runs()
{
    for(int i=1;i<=n;i++)
    {
        int l1=i,r1=run[i],l2=l1-lcs(l1-1,r1),r2=r1+lcp(l1,r1+1);
        if(r2-l2+1>=(r1-l1+1)*2) ans.push_back(runs(l2,r2,r1-l1+1));
    }
}
int main()
{
    scanf("%s",str+1);
    n=strlen(str+1);
    for(int i=1;i<=n;i++) s[i]=str[i]-'a'+1;
    init();
    lyndon();
    get_runs();
    for(int i=1;i<=n;i++) s[i]=27-s[i];
    lyndon();
    get_runs();
    sort(ans.begin(),ans.end());
    ans.erase(unique(ans.begin(),ans.end()),ans.end());
    printf("%d\n",(int)ans.size());
    for(auto u:ans) printf("%d %d %d\n",u.i,u.j,u.p);
    return 0;
}
posted @ 2021-03-06 13:50  Flying2018  阅读(369)  评论(0编辑  收藏  举报