Lyndon分解

Lyndon分解

这是什么

  Lyndon串:对于一个串 s 1 … n s_{1…n} s1n的所有后缀 s 1 … n s_{1…n} s1n的字典序最小,此时,我们称 s 1 … n s_{1…n} s1n是Lyndon串。

  近似Lyndon串:设一个串s是Lyndon串,s’是s的前缀,那么形如ssss’的形式的串称之为近似Lyndon串。

  Lyndon分解:将一个串拆成若干个Lyndon串的分解方式。
  例如:cuucuucud拆成cuu,cuu,cud

怎么求

  设 i 是目前的起始位置,令j = i, k = i + 1,比较 s j s_{j} sj s k s_{k} sk的大小:

  1. 如果 s j = s k s_{j} = s_{k} sj=sk,那么说明在这一位置无法比较出大小,继续向后比较,j, k同时向后移动。

  2. 如果 s j < s k s_{j} < s_{k} sj<sk,那么说明 s u f k − ( j − i ) suf_{k - (j - i)} sufk(ji) s u f i suf_{i} sufi字典序大,且其中以 s u f k − ( j − i ) … … s u f k suf_{k - (j - i)}……suf_{k} sufk(ji)sufk都要对应地比 s u f i … … s u f j suf_{i}……suf_{j} sufisufj大,那么也就必 s u f i suf_{i} sufi大。此时,将 j 移动到 i,从头开始新一轮的比较。

  3. 如果 s j > s i s_{j}>s_{i} sj>si,那么说明 s u f k − ( j − i ) suf_{k - (j - i)} sufk(ji) s u f i suf_{i} sufi字典序小,此时意味着当前Lyndon串再延续下去就不合法了。此时,终止内层循环。

  在每一轮循环的结束,我们将得到一个近似Lyndon串 s i … k − 1 s_{i…k-1} sik1,其中真正的Lyndon串的大小为 k - j ,那么我们每隔 k - j 位做一次分割即可。

  分割完成后,将 i 跳到下一个起始位置即可(注意近似Lyndon串的"零头"要重新做)。

洛谷例题

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#define LL long long
using namespace std;
const int N = 5e6 + 10;
char s[N];
int main()
{
	int ans = 0, n;
	scanf("%s", s + 1);
	n = strlen(s + 1);
	for(int i = 1; i <= n; )
	{
		int j = i;
		int k = i + 1;
		while(k <= n && s[j] <= s[k])
		{
			if(s[j] == s[k])	j++;
			else	j = i;
			k++;
		}
		while(i <= j)
		{
			i += k-j;
			ans = ans ^ (i - 1);
		}
	}
	cout<<ans;
	return 0;
 } 
有什么用

求最小后缀

HDU-6761

题意:对于一个串的每一个前缀,求其最小后缀。

思路:求串的Lyndon分割,如果 s j < s k s_{j} < s_{k} sj<sk那么跳回到当前近似Lyndon串的串首,否则,由 j 位置顺延一个Lyndon串(也就是回到当前近似Lyndon串的最后一个循环节的起始位置)。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#define LL long long
using namespace std;
const int N = 1e6 + 10;
const LL mod = 1e9 + 7; 
LL f[N], d[N];
char s[N];
int main()
{
	f[1] = 1;
	for(int i = 2; i <= 1e6; i++)
		f[i] = f[i - 1] * (LL)1112 % mod; 
	int T;
	scanf("%d", &T);
	while(T)
	{
		T--;
		LL ans = 0;
		scanf("%s", s + 1);
		int n = strlen(s + 1);
		d[1] = 1;
		for(int i = 1; i <= n;)
		{
			int j = i;
			int k = i + 1;
			while(k <= n && s[j] <= s[k])
			{
				if(s[j] == s[k])	d[k] = d[j] + k - j, j++;	//顺延一个串的位置 
				else	d[k] = i, j = i;	//回串首 
				k++;
			}
			d[k] = k; //下一个串的开始 
			while(i <= j)	i += k - j;
		}
		for(int i = 1; i <= n; i++)	ans = (ans + d[i] * f[i] % mod) % mod;
		cout<< ans <<endl;
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值