Lyndon word问题的Duval算法入门详解

版权声明:转载请注明原出处啦QAQ(虽然应该也没人转载): https://blog.csdn.net/hzk_cpp/article/details/86102813

一.Lyndon串的定义.

对于两个串s1与s2,令 n = s 1 , m = s 2 , x = m i n ( n , m ) n=|s1|,m=|s2|,x=min(n,m) s 1 = s 2 s1=s2 仅当 n = m n=m s 1 [ 1.. n ] = s 2 [ 1.. m ] s1[1..n]=s2[1..m] s 1 < s 2 s1<s2 仅当 s 1 [ 1.. x ] < s 2 [ 1.. x ] s1[1..x]<s2[1..x] ,或 s 1 [ 1.. x ] = s 2 [ 1.. x ] s1[1..x]=s2[1..x] n < m n<m .

一个字符串s为Lyndon串仅当s在它的所有后缀中最小.


二.Lyndon串的一个性质.

性质1:当u,v为Lyndon串且u<v时,uv(u与v相连接)也为Lyndon串.

证明如下:
首先显然uv大于任意一个u的后缀与v连接.
然后因为 u &lt; v &lt; v [ i . . v ] u&lt;v&lt;v[i..|v|] ,所以 u v &lt; v [ i . . v ] uv&lt;v[i..|v|] .
综上所述,uv小于所有它的后缀,即uv为一个Lyndon串.
证毕.


三.Lyndon word问题.

Lyndon word问题是指,将一个字符串s分解成   s 1 s 2 . . . s k \ s_1s_2...s_k ,其中   s i \ \forall s_i 为Lyndon串, s i s i + 1 s_i\geq s_{i+1} ,求拆分方案.

这个问题首先具有存在性,证明:
首先当一个串只有一个字符时,显然这个串为一个Lyndon串.
当一个串S不是Lyndon串时,它显然可以被分成 S |S| 个单个字符组成的字符串合并的形式.
根据性质1,这些串中只要有 s i &lt; s i + 1 s_i&lt;s_{i+1} 的情况,就可以被合并成一个新的Lyndon串.
证毕.

这个问题也具有唯一性,用反证法证明如下:
设若有两种方案,设两种方案不同的位置为 s i s_i ,两种方案分别为 s i s_i s i s&#x27;_i ,且 s i &gt; s i |s_i|&gt;|s&#x27;_i| .
我们令 s i = s i s i + 1 . . . s k s k + 1 [ 1.. j ] s_i=s&#x27;_is&#x27;_{i+1}...s&#x27;_{k&#x27;}s&#x27;_{k&#x27;+1}[1..j] ,其中 s k + 1 [ 1.. j ] s&#x27;_{k&#x27;+1}[1..j] 表示 s k + 1 s&#x27;_{k&#x27;+1} 的一个前缀.
根据定义, s i &lt; s k + 1 [ 1.. j ] s k + 1 s i &lt; s i s_i&lt;s&#x27;_{k+1}[1..j]\leq s&#x27;_{k+1}\leq s&#x27;_i&lt;s_i .
证毕.


四.Duval算法.

Duval算法是用来在线性求解拆分方式的一种算法,它需要用到下面这个性质2
当字符串s与字符c组成的字符串sc是某个Lyndon串的前缀时,对于字符 d &gt; c d&gt;c 满足sd为Lyndon串.

证明如下:
首先显然对于一个Lyndon串的前缀s s s [ i . . s + 1 ] s\leq s[i..|s|+1] ,即s小于等于它的所有后缀.
所以s与sc必然都满足小于等于它们的所有后缀,加入一个大于c的字符自然也就变成了Lyndon串.
证毕.

那么我们就可以开始讲解Duval算法了,Duval算法只需要维护三个指针i,j,k就可以求出一个串的Lyndon分解.

首先,i表示当前未确定拆分的第一个点,即 s [ 1.. i 1 ] s[1..i-1] 已经被确定了拆分方式.

然后,我们设 s [ i . . k 1 ] = t 1 t 2 . . t h v s[i..k-1]=t_1t_2..t_hv ,其中 t 1 = t 2 = . . . = t h t_1=t_2=...=t_h 都是Lyndon串,v必须是一个 t i t_i 的前缀,也就是说 s [ i . . k 1 ] s[i..k-1] 必须是由串 t i t_i 循环构成的.

这时我们再维护一个指针 j = k t i j=k-|t_i| ,也就是k对应在 t h t_h 这个串的字符.很容易发现我们要维护串 s [ i . . k ] s[i..k] 是否可被分成一个Lyndon串循环构造的性质,分三种情况:
1. s [ k ] = s [ j ] s[k]=s[j] ,也就是说它还是可以被循环构造的,这时候让j和k自增1就好了.
2. s [ k ] &gt; s [ j ] s[k]&gt;s[j] ,显然 t 1 t 2 . . t h v s [ k ] t_1t_2..t_hvs[k] 为一个Lyndon串,所以将这个串合并就好,即让j变成i,k自增1.
3. s [ k ] &lt; s [ j ] s[k]&lt;s[j] ,发现这时候合并并不能变成Lyndon串,所以停止操作记录每一个位置,并将i指针变成v的开头.

时间复杂度为线性是因为一个字符最多会被扫到两边,所以时间复杂度为 O ( n ) O(n) .

代码如下:

void Lyndon_word(){
  int j,k;
  for (int i=1;i<=n;){
  	for (j=i,k=i+1;k<=n&&s[k]>=s[j];++k)      //一直循环直到串结束或情况3出现 
  	  s[k]>s[j]?j=i:++j;      //情况1与情况2 
  	for (;i<=j;i+=k-j)
  	  x[++ts]=i+k-j-1;      //记录右端点 
  }
}



五.例题与代码.

例题:LOJ129.
代码如下:

#include<bits/stdc++.h>
  using namespace std;

#define Abigail inline void
typedef long long LL;

const int N=1<<20;

char s[N+9];
int n,x[N+9],ts;

void Lyndon_word(){
  int j,k;
  for (int i=1;i<=n;){
  	for (j=i,k=i+1;k<=n&&s[k]>=s[j];++k)      //一直循环直到串结束或情况3出现 
  	  s[k]>s[j]?j=i:++j;      //情况1与情况2 
  	for (;i<=j;i+=k-j)
  	  x[++ts]=i+k-j-1;      //记录右端点 
  }
}

Abigail into(){
  scanf("%s",s+1);
  n=strlen(s+1);
}

Abigail work(){
  Lyndon_word();
}

Abigail outo(){
  for (int i=1;i<=ts;++i)
    printf("%d ",x[i]);
}

int main(){
  into();
  work();
  outo();
  return 0;
}

猜你喜欢

转载自blog.csdn.net/hzk_cpp/article/details/86102813