LOJ129 Lyndon 分解

风流意气都作罢 提交于 2020-11-06 04:47:04

Lyndon 分解

样例

样例输入 1 ababa 样例输出 1 2 4 5

样例输入 2 bbababaabaaabaaaab 样例输出 2 1 2 4 6 9 13 18

样例输入 3 azAZ0129 样例输出 3 2 4 8

数据范围与提示

$1\le |s| \le 2^{20}$

OZY的题解

冷门东西,但是今天考到了,做个记录。

记号

$s[l : r]$ 表示字符串$s$ 从第$l$ 个字符到第$r$ 个字符的子串(从$1$ 开始标号),$|s|$ 表示$s$ 的长度。

当$l = 1$ 时$s[l : r]$ 简写为$s[: r]$ ,表示$s$ 的一个前缀。当$r = |s|$ 时$s[l : r]$ 简写为$s[l :]$ ,表示$s$ 的一个后缀。

$st, s +t$ 表示两个字符串$st$ 的拼接,$s^k$ 表示$k$ 个$s$ 拼起来,特别地,$s^{\infty}$ 表示$s$ 的无限循环。

定义

Lyndon 串:如果一个串$s$ 满足$s = \min{s[i :]|1 \le i \le |s|}$ 那么我们称串$s$ 为Lyndon 串。定义字符串的大小关系就是字典序的大小关系

性质

当$u,v$均为Lyndon Words,且$u<v$,那么$uv$也是一个Lyndon Words。

证明还是比较显然的,这里就不证了

Lyndon 划分

对于一个字符串$s$,如果一个划分将它分成若干个串$s=p_1+p_2+p_3+\dots+p_n$,使得每个$p$都是Lyndon Words,且$p_i\ge p_{i+1}$,则这个划分是Lyndon 划分

可以发现,一个字符串,一定存在一种Lyndon 划分,证明可以用构造法来证明。

一开始先所有$p$设为单个字母。显然,这是满足第一个条件的,只需要再满足递减的关系就可以了。

可以发现若$p_i<p_{i+1}$,它们合起来也是一个Lyndon Words。

并且可以发现,对于一个串,他的Lyndon 划分是唯一的。

算法

目的是求出$r[i]$,表示第$i$个字符所属Lyndon Words的右端点的下一个位置。

就是维护类似单调栈的东西就可以了。单调栈内维护的是属于同一Lyndon Words的节点,换句话说如果不满足字典序的单调递增,就要清空。发现这就是维护定义……很显然啊。

复杂度瓶颈在于比较后缀大小,用后缀树(DC3后缀数组+笛卡尔树)和±1RMQ即可$O(n)$。这里只给出不能AC的hash做法,$O(n\log n)$。

当然这题还有$O(n)$的Duval算法,但是我觉得没必要学。

#include<bits/stdc++.h>
#define co const
#define il inline
using namespace std;
typedef unsigned long long ULL;

co int N=(1<<20)+10;
co ULL base=131;
char str[N];int n;
ULL pw[N],hs[N];

il ULL calc(int l,int r){
	return hs[r]-hs[l-1]*pw[r-l+1];
}
int lcp(int x,int y){ // str[x:],str[y:]
	int l=0,r=n-max(x,y)+1;
	while(l<r){
		int mid=(l+r+1)>>1;
		if(calc(x,x+mid-1)==calc(y,y+mid-1)) l=mid;
		else r=mid-1;
	}
	return l;
}
il bool cmp(int x,int y){ // str[x:]<str[y:]
	int len=lcp(x,y);
	if(len==n-max(x,y)+1) return x>y; // partition by >=
	return str[x+len]<str[y+len];
}

int r[N],st[N],top;
int main(){
	scanf("%s",str+1),n=strlen(str+1);
	pw[0]=1;
	for(int i=1;i<=n;++i){
		pw[i]=pw[i-1]*base;
		hs[i]=hs[i-1]*base+str[i];
	}
	for(int i=1;i<=n;++i){
		while(top&&cmp(i,st[top])) r[st[top--]]=i;
		st[++top]=i;
	}
	while(top) r[st[top--]]=n+1;
	for(int i=1;i<=n;i=r[i]) printf("%d ",r[i]-1);
	return 0;
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!