最小表示法与Lyndon 分解 学习笔记

最小表示法

最小表示法指一个串所有循环同构串中字典序最小的一个
可以用这样的算法在线性时间内求出最小表示法

首先复制一份在最后
用两个指针一前一后进行扫描,找到第一个不一样的地方,比如 \(a[i+k]>a[j+k]\),这时说明 \(i...k\) 都不可能成为最小表示法了,那么将 \(i\) 移动到 \(i+k+1\) 即可

int main(){
	n=read();
	for(int i=1;i<=n;i++){
		a[i+n]=a[i]=read();
	}
	int i=1,j=2;
	while(i<=n&&j<=n){
		int k=0;
		for(;k<n&&a[i+k]==a[j+k];k++);
		if(k==n)break;
		if(a[i+k]>a[j+k]){
			i=i+k+1;
			if(i==j)i++;
		}
		if(a[i+k]<a[j+k]){
			j=j+k+1;
			if(i==j)j++;
		}
	}
	ans=min(i,j);
	for(int i=ans;i<=ans+n-1;i++){
		printf("%d ",a[i]);
	}
}

Lyndon 分解

首先定义是最小表示法(严格)的字符串为 \(Lyndon\)
Lyndon 分解指的是对于一个字符串分解为若干子串,使得每一个子串都是 \(Lyndon\) 串,并且字典序不升

可以证明每个串都有唯一的分解方式

  • 引理 1:如果 \(u\)\(v\) 都是 \(Lyndon\) 串并且 \(u<v\),则 \(uv\) 也是 \(Lyndon\) 串。

  • 引理2:若字符串 \(v\) 和字符 \(c\) 满足 \(vc\) 是某个 \(Lyndon\) 串的前缀,则对于字符 \(d>c\)\(vd\)\(Lyndon\) 串。

可以画出来手模,这里就不放证明了

  • \(Duval\) 算法:

可以结合 这篇博客 里的图理解

首先将串维护成三个部分 \(s_1,s_2,s_3\),第一部分已经分解完成,\(s_2\) 是一个循环节为 \(l\) 的串(末尾为循环节前缀),第三个是待处理部分
维护出三个指针 \(i,j,k\) 分别指向目前已经分解完成的部分,上一个循环节这一位置,以及未处理的第一个位置
现在试图把 \(s_3\) 的第一个字符放入 \(s_2\)
如果 \(a_j==a_k\),那么满足循环性质,\(j\) 指针后移即可
如果 \(a_j<a_k\),并不满足性质,那么直接将 \(s_2\)\(s_k\) 合并在一起成为一个新的 \(Lyndon\) 串,将 \(j\) 回归到 \(s_2\) 开头
如果 \(a_j>a_k\),意味着整个块满足了性质,那么把每一个循环节拿出来当做分解出来的块,即移动 \(i\) 的过程

  • 最小表示

\(Lyndon\) 分解可以求出最小表示,可以这样来求:
\(s\) 复制一倍在后面,寻找分解中跨过拼接点的一个 \(Lyndon\) 串,那么这个串的起点就是最小表示法的起点

int main(){
	scanf("%s",a+1);
	n=strlen(a+1);
	for(int i=1;i<=n;){
		int j=i,k=i+1;
		while(k<=n&&a[k]>=a[j]){
			if(a[k]>a[j])j=i;
			else j++;
			k++;
		}
		while(i<=j){
			ans^=i+k-j-1;
			i+=k-j;
		}
	}
	cout<<ans;
	return 0;
}
posted @ 2021-12-08 20:49  y_cx  阅读(123)  评论(0编辑  收藏  举报