1 条题解

  • 0
    @ 2025-8-24 21:24:18

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar xht
    好想爱这个世界啊

    搬运于2025-08-24 21:24:17,当前版本为作者最后更新于2020-02-22 20:48:46,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    怎么没人用 Lyndon 分解求最小表示法呢?它这么冷门的嘛...

    Lyndon 串

    对于一个字符串,若其本身就是其最小后缀,则称它为 Lyndon 串。

    形式化地,对于长度为 nn 的字符串 ss,若满足对于 i[2,n]i \in [2,n],都有 s<s[i:n]s < s[i:n],则称其为 Lyndon 串。

    Lyndon 分解

    任意一个字符串都可以被唯一的分解成若干个字典序非严格递减的 Lyndon 串。

    形式化地,对于长度为 nn 的字符串 ss,存在唯一的若干个 Lyndon 串 t1mt_{1\dots m},满足 s=t1+t2++tms=t_1 + t_2 + \cdots + t_mt1t2tmt_1 \ge t_2 \ge \cdots \ge t_m

    Duval 算法

    Duval 算法可以在 O(n)\mathcal O(n) 的时间求出一个字符串的 Lyndon 分解。

    维护三个指针 i,j,ki,j,k,这三个指针将整个字符串分成了四个部分 s[1:i1],s[i,k1],s[j,k1],s[k,n]s[1:i-1], s[i,k-1], s[j,k-1], s[k,n]

    • s[1,i1]s[1,i-1]:这部分的 Lyndon 分解已经完成。
    • s[i,k1]s[i,k-1]:这部分可以被表示成 tc+vt^c + v,其中 tt 是一个 Lyndon 串,tct^c 表示 tt 循环 cc 次,vvtt 的一个可空真前缀。
    • s[j,k1]s[j,k-1]:注意这部分是包含在上一个部分中的,其中 j=ktj = k - |t|
    • s[k,n]s[k,n]:这部分还未处理,此时正在考虑 kk

    考虑 kk 有三种情况:

    • sj=sks_j = s_k:可以将 tt 继续循环下去,因此 jj 往后移一位,考虑下一个 kk
    • sj<sks_j < s_k:可以将 tc+v+skt^c + v + s_k 合并为一个 Lyndon 串,因此 jj 设为 ii,考虑下一个 kk
    • sj>sks_j > s_k:可以将 tt 单独作为 Lyndon 分解中的一个串,然后从 vv 的开头开始重新考虑,因此 ii 设为 vv 的开头,j,kj,k 对应设为 i,i+1i,i+1

    【模板】P6114 【模板】Lyndon 分解

    const int N = 5e6 + 7;
    int n, ans;
    char s[N];
    
    int main() {
    	rds(s, n);
    	int i = 1;
    	while (i <= n) {
    		int j = i, k = i + 1;
    		while (k <= n && s[j] <= s[k]) j = s[j] == s[k++] ? j + 1 : i;
    		while (i <= j) i += k - j, ans ^= i - 1;
    	}
    	print(ans);
    	return 0;
    }
    

    最小表示法

    一个字符串的最小表示定义为其所有循环同构中字典序最小的串。

    形式化地,对于长度为 nn 的字符串 ss,若 p[1,n]p \in [1,n] 满足对于 i[1,n]i \in [1, n],都有 s[p:n]+s[1:p1]s[i:n]+s[1:i1]s[p:n] + s[1:p-1] \le s[i:n] + s[1:i-1],则称 s[p:n]+s[1:p1]s[p:n] + s[1:p-1]ss 的最小表示。

    最小表示法可以使用 Lyndon 分解求出。

    对于长度为 nn 的字符串 ss,设 t=s+st = s + s,对 tt 进行 Lyndon 分解,找到首字符位置 n\le n 且最大的 Lyndon 串,这个串的首字符即最小表示法的首字符。

    【模板】P1368 工艺 /【模板】最小表示法

    const int N = 6e5 + 7;
    int n, ans, s[N];
    
    int main() {
    	rd(n);
    	for (int i = 1; i <= n; i++) rd(s[i]), s[i+n] = s[i];
    	int i = 1;
    	while (i <= n) {
    		int j = i, k = i + 1;
    		while (k <= n * 2 && s[j] <= s[k]) j = s[j] == s[k++] ? j + 1 : i;
    		while (i <= j) i += k - j, ans = i <= n ? i : ans;
    	}
    	if (ans == 0) ans = n;
    	for (int i = 1; i <= n; i++) print(s[ans-1+i], " \n"[i==n]);
    	return 0;
    }
    

    【例题】UVA719 Glass Beads

    与上一题不同的是,这题要求位置最靠前。

    只有一种情况下两道题的答案不一样,那就是字符串恰好为一个循环串的时候。

    那么我们换一个写法即可。

    const int N = 2e4 + 7;
    int n, ans;
    char s[N];
    
    inline void solve() {
    	rds(s, n);
    	for (int i = 1; i <= n; i++) s[i+n] = s[i];
    	int i = 1;
    	while (i <= n) {
    		ans = i;
    		int j = i, k = i + 1;
    		while (k <= n * 2 && s[j] <= s[k]) j = s[j] == s[k++] ? j + 1 : i;
    		while (i <= j) i += k - j;
    	}
    	print(ans);
    }
    
    int main() {
    	int T;
    	rd(T);
    	while (T--) solve();
    	return 0;
    }
    

    参考资料

    • 1

    信息

    ID
    366
    时间
    1000ms
    内存
    512MiB
    难度
    5
    标签
    递交数
    0
    已通过
    0
    上传者