Lyndon分解
我们定义一个字符串是 \(\text{Lyndon}\) 串,当且仅当这个串本身就是它的最小后缀。
首先很显然的是,若 \(S\) 是一个 \(\text{Lyndon}\) 串对于 \(∀i∈[1,|S|],S[i]\ge S[1]\)。
引理1:如果 \(u\) 和 \(v\) 都是 \(\text{Lyndon}\) 串,并且 \(u<v\),则 \(uv\) 也是 \(\text{Lyndon}\) 串。
我们设 \(u'\) 是 \(uv\) 的后缀且 \(len(u')>len(v)\),\(v'\) 是 \(uv\) 的后缀且 \(len(v')\le len(v)\)。
由于 \(u,v\) 都是 \(\text{Lyndon}\) 串,则 \(u<u'<v<v'\),又 \(uv<u\),所以可得 \(uv\) 也是 \(\text{Lyndon}\) 串。
定义一个串 \(S\) 的 \(\text{Lyndon}\) 分解为一个字符串序列 \(S_1,S_2,S_3...S_m\),且要满足:
- \(∀i∈[1,m]\),满足 \(S_i\) 是 \(\text{Lyndon}\) 串。
存在性证明
起初我们可以将 \(S\) 分为 \(|S|\) 个 \(\text{Lyndon}\) 串,然后每次找到相邻的两个串且 \(S_i<S_{i+1}\),将它们合并成一个串。重复这一操作,最后一定满足任意的 \(S_i\ge S_{i+1}\)。
唯一性证明
假设对于 \(S\) 存在两个 \(\text{Lyndon}\) 分解:
我们设 \(len(S_i)>len(S'_i)\)。
那么 \(S_i=S'_iS'_{i+1}S'_{i+2}...S'_{k-1}S''_k\)。其中 \(S''_k\) 是 \(S'_k\) 的前缀。
由 \(\text{Lyndon}\) 串的性质得:
\(S_i<S_i\) 矛盾,假设不成立。所以可得 \(\text{Lyndon}\) 分解唯一。
引理2:若字符串 \(v\) 和字符 \(c\) 满足 \(vc\) 是某个 \(\text{Lyndon}\) 串的前缀,则对于字符 $ d>c$ 有 \(vd\) 是 \(\text{Lyndon}\) 串。
设这个 \(\text{Lyndon}\) 串是 \(vct\),\(v\) 的一个后缀为 \(v'\)。
那么可得: \(v'ct > vct\),也就是 \(v'c \ge v\)。
所以 \(v'd > v'c \ge v\)。
又因为 \(c > v[1]\),所以 \(d>c>v[1]\),\(vd\) 是 \(\text{Lyndon}\) 串。
性质1:若字符串 \(S\) 为 \(\text{Lyndon}\) 串,那么它没有 \(\text{border}。\)
证明:假设 \(S\) 有 \(\text{border}\),那么有一个后缀等于前缀,于是这个后缀就小于 \(S\) 了,假设不成立。
性质2:如果 \(S\) 是 \(\text{Lyndon}\) 串,\(s=uv\) 且 \(|u|>0,|v|>0\),满足 \(u<v\)。
证明:这个就是反过来的引理1,因为 \(u<uv\) 且 \(uv>v\) 所以 \(u<v\)。
性质3:如果 \(|s|>2\),\(s\) 是一个 \(\text{Lyndon}\) 串充要条件是 \(\exists ~u,v\) 使得 \(s=uv\),且 \(|u|>0,|v|>0,u<v\) 并且 \(u,v\) 都是 \(\text{Lyndon}\) 串。
\(\text{Duval}\) 算法
\(\text{Duval}\) 可以在 \(O(n)\) 的时间内求出一个串的 \(\text{Lyndon}\) 分解。
首先我们介绍另外一个概念:如果一个字符串 \(t\) 能够分解为 \(t=ww...\bar w\) 的形式,其中 \(\bar w\) 是一个 \(\text{Lyndon}\) 串,而 \(\bar w\) 是 \(w\) 的前缀( 可能是空串),那么称 \(t\) 是近似简单串,或者近似 \(\text{Lyndon}\) 串。一个 \(\text{Lyndon}\) 串也是近似 \(\text{Lyndon}\) 串。
\(\text{Duval}\) 算法运用了贪心的思想。算法过程中我们把串 \(S\) 分成三个部分 \(S=S_1S_2S_3\),其中 \(S_1\) 是一个 \(\text{Lyndon}\) 串,它的 \(\text{Lyndon}\) 分解已经记录; \(S_2\) 是一个近似 \(\text{Lyndon}\) 串; \(S_3\) 是未处理的部分。
整体描述一下,该算法每一次尝试将 \(S_3\) 的首字符添加到 \(S_2\) 的末尾。如果 \(S_2\) 不再是近似 \(\text{Lyndon}\) 串,那么我们就可以将 \(S_2\) 截出一部分前缀(即 \(\text{Lyndon}\) 分解)接在 \(S_1\) 末尾。
我们来更详细地解释一下算法的过程。定义一个指针 \(i\) 指向 \(S_2\) 的首字符,则 \(i\) 从 \(1\) 遍历到 \(n\) (字符串长度)。在循环的过程中我们定义另一个指针 \(k\) 指向 \(S_3\) 的首字符,指针 \(j\) 指向 \(S_2\) 中我们当前考虑的字符(意义是 \(k\) 在 \(S_2\) 的上一个循环节中对应的字符)。我们的目标是将 \(S[k]\) 添加到 \(S_2\) 的末尾,这就需要将 \(S[k]\) 与 \(S[j]\) 做比较:
- 如果 \(S[j]=S[k]\),则将 \(S[k]\) 添加到 \(S_2\) 末尾不会影响它的近似简单性。于是我们只需要让指针 \(j,k\) 自增(移向下一位)即可。
- 如果 \(S[j]<S[k]\),那么 就变成了一个 \(\text{Lyndon}\) 串,于是我们将指针 \(k\) 自增,而让 \(j\) 指向 \(S_2\) 的首字符,这样 \(S_2\) 就变成了一个循环次数为 \(1\) 的新 \(\text{Lyndon}\) 串了。
- 如果 \(S[j]>S[k]\),则 \(S_2S[k]\) 就不是一个近似简单串了,那么我们就要把 \(S_2\) 分解出它的一个 \(\text{Lyndon}\) 子串,这个 \(\text{Lyndon}\) 子串的长度将是 \(k-j\),即它的一个循环节。然后把 \(S_2\) 变成分解完以后剩下的部分,继续循环下去(注意,这个情况下我们没有改变指针 \(j,k\)),直到循环节被截完。对于剩余部分,我们只需要将进度“回退”到剩余部分的开头即可。
代码:
#include<bits/stdc++.h>
#define pc(x) putchar(x)
using namespace std;
void write(int x)
{
if(x<0){x=-x;putchar('-');}
if(x>9)write(x/10);
putchar(x%10+48);
}
int n,ans;
char s[5000005];
int main()
{
scanf("%s",s+1);n=strlen(s+1);
for(int i=1;i<=n;)
{
int j=i,k=i+1;
while(k<=n&&s[j]<=s[k])
{
if(s[j]<s[k])j=i;
else ++j; ++k;
}
while(i<=j)
{
ans^=i+k-j-1;
i+=k-j;
}
}write(ans),pc('\n');
return 0;
}
这玩意根本不会考到好吧(