ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

【题解】Luogu5420 / UOJ202 / LOJ2991 香山的树 另解

2021-12-16 08:33:39  阅读:143  来源: 互联网

标签:word int 题解 modmodint Luogu5420 Lyndon 另解 necklace operatorname


Luogu5420 / UOJ202 / LOJ2991 香山的树 另解

这个题大部分人都是用 KMP 自动机+dp 做的。这里是一个不用 KMP 的做法。

记号

字符集为 \(\Sigma\),假设最小的字母是 \(\mathrm a\),最大的字母是 \(\mathrm z\)。

\(\operatorname{pref}_i\) 表示长度为 \(i\) 的前缀,\(\operatorname{suf}_i\) 表示长度为 \(i\) 的后缀。

一个 Lyndon word 是一个字符串,满足其任意一个非空且不等于自身的后缀小于他自身。

一个 necklace 是一个字符串,他是自己的最小循环表示。

显然,Lyndon word 一定是 necklace,并且一个 necklace 一定可以表示为一个 Lyndon word 的若干次重复。

比如:aab 既是 Lyndon word 又是 necklace,aabaab 不是 Lyndon word 但是是 necklace。

记 \(\operatorname{minrot}(a)\) 为 \(a\) 的最小表示,\(\operatorname{lyn}(a)\) 为 \(a\) 的最长 Lyndon 前缀的长度。

记 \(\operatorname{largestLyndon}(a)\) 表示不超过 \(a\) 的最大 Lyndon word,\(\operatorname{largestNecklace}(a)\) 表示不超过 \(a\) 的最大 necklace。

简要思路

先实现 \(O(n^2)\) 次运算求出一个 Lyndon word \(S\) 在长为 \(|S|\) 的 Lyndon word 中排名第几(具体方法见下文)。

算法一:求出 \(S\) 在长度 \(\leq n\) 的 Lyndon word 中的排名

先枚举长度 \(l\),然后分类讨论:\(l\leq |S|\):\(rk(\operatorname{largestLyndon}(\operatorname{pref}(S))\);\(l > |S|\):\(rk(\operatorname{largestLyndon}(S\mathrm a^{l - |S|}))\)。\(O(n^3)\) 次运算。

算法二:求出长度 \(\leq n\) 的以 \(S\) 为前缀的 Lyndon word 个数

先枚举长度 \(l\),其贡献为:\(rk(\operatorname{largestLyndon}(Sz^{l-|S|}))-rk(\operatorname{largestLyndon}(Sa^{l-|S|}))\)。\(O(n^3)\) 次运算。

接下来有两种方法解决这个问题:

  1. 逐位确定

    每次算出以 \(p\) 为前缀的 Lyndon word 的个数 \(u\),讨论 \(k\) 和 \(u\) 的关系:

    如果 \(k > u\),则表示 \(p\) 比答案的同样长度的前缀小,\(k\) 减去 \(u\),\(p\) 变大 \(1\) 步(这里需要先删去 \(p\) 后缀的所有 \(\mathrm z\))。如果 \(p\) 无法再变大就输出 \(-1\)。

    否则 \(p\) 肯定是答案的前缀,在 \(p\) 的后面加上 \(\mathrm a\)。

    重复这个过程直到 \(p\) 是答案。考虑到整个过程中 \(p\) 的字典序递增,所以只会执行 \(O(n|\Sigma|)\) 次排名的查询。

    因为 \(k\) 只有 \(10^{15}\),所以我们算排名的时候可以对一个比较大的数取模(我的代码中对 \(2^{64}\) 取模),这样不用高精度。

    时间复杂度:\(O(n^4|\Sigma|)\),和 KMP 的做法是一样的。

  2. 二分

    每次二分确定一位。时间复杂度:\(O(n^4 \log |\Sigma|)\) 次运算,但是容易发现需要高精度。高精度的位数是 \(\dfrac {\log |\Sigma|^n} {\log 2^w} = n \log |\Sigma| / w\)(压位高精)。总的时间复杂度是 \(O(n^6 \log^3 |\Sigma| / w^2)\)。在 \(n\) 更小,\(\Sigma\) 更大的时候,这个方法比较有优势。

接下来说说怎么求出一个 Lyndon word \(S\) 在长为 \(|S|\) 的 Lyndon word 中排名第几。

\(O(n^2)\) ranking

Lemma 1:若 \(a\) 是一个 necklace。如果 \(x>a_j\),那么 \(a_ia_{i+1}\ldots a_{j-1}x\) 比 \(a\) 大。

考虑 \(a_ia_{i+1}\ldots a_j \geq a_1a_2\ldots a_{j-i+1}\)。易证。

算法三:在 \(O(n)\) 时间内求出 \(\operatorname{minrot}(a)\) 和 \(\operatorname{lyn}(a)\),或者判断一个串是不是一个 necklace。

其实是魔改 Duval 算法。初始时 \(p=1\),指针 \(q=1\)。\(q\) 在 \(a\) 上往后扫,如果 \(a_q > a_{q-p}\) 说明存在更长 Lyndon 前缀,更新 \(p\);否则说明无法往后扩展,直接退出。如果没有扫完就退出了那么肯定不是 necklace。

Lemma 2:如果 \(\operatorname{lyn}(a) = p\),则 \(\forall b < \operatorname{suf}_{n-p}\),有 \(\operatorname{lyn}(\operatorname{pref}_p b)=p\)。

根据算法三易证。

算法四:在 \(O(n^2)\) 时间内求出 \(\operatorname{largestLyndon}\) 和 \(\operatorname{largestNecklace}\)。

根据 Lemma 2,如果 \(a\) 不是一个 necklace,且 \(p=\operatorname{lyn}(a)\),那么大于 \(\operatorname{pref}_{p-1}(a_p-1)\mathrm k^{n-p}\) 的串一定不是 necklace。如果一个串是 Lyndon word,那么这个串一定是一个 necklace。

所以我们每次求出 \(p\),然后更新 \(a\),直到 \(a\) 是 necklace 或者 Lyndon word 为止。

注意到在一个 Lyndon word 后面添加若干个 \(\mathrm z\) 之后他还是一个 Lyndon word,所以如果在此过程中 \(p\) 增大了,那么 \(a\) 一定是 necklace 或者 Lyndon word 了。考虑到 \(p\) 一定不会不变,所以在没找到答案的时候 \(p\) 一定是严格递减的。所以至多更新 \(n\) 次 \(a\)。所以时间复杂度 \(O(n^2)\)。


令 \(a\) 是一个 necklace。记 \(B_{t, j} (t \geq j)\) 为长度为 \(t\) 并且和 \(a\) 有长度至少为 \(j\) 的共同前缀并且每个后缀都大于 \(a\) 的串的个数。

算法五:在 \(O(n^2)\) 时间内求出 \(B\)。

显然初始状态为 \(B_{0,0}=1\),\(B_{i,i}=0\ (i\geq1)\)。

然后我们对于 \(B_{t,j}\) 我们考虑第 \(j+1\) 位的选择。根据 Lemma 1,第 \(j+1\) 位不能取小于 \(a_{j+1}\)​​ 的值。所以我们可以得到转移:

\[B_{t,j} = B_{t, j+1} + (z - a_{j+1}) \cdot B_{t - j - 1,0} \]

直接 dp,\(O(n^2)\)。


记 \(T(a)\) 表示最小循环表示不大于 \(a\)​​​ 并且和 \(a\) 等长的字符串的数量。

注意到 \(T(a) = T(\operatorname{largestNecklace}(a))\),所以我们接下来只考虑 \(a\) 为 necklace 的情况。

算法六:在 \(O(n^2)\) 时间内求出 \(T(a)\)。

因为 \(T(a) = T(\operatorname{largestNecklace}(a))\),所以我们先把 \(a\) 变成 \(\operatorname{largestNecklace}(a)\)。

我们考虑把所有最小循环小时不大于 \(a\) 的字符串 \(b\) 分组。

对于任意一个 \(b\),我们一定可以找到一个循环移位 \(b_ib_{i+1}\ldots b_nb_1b_2\ldots b_{i-1}\) 使得 \(b \leq a\)。因为可能有多个,所以我们取最小的 \(i\)​。我们把 \(b\) 归入 \(T_{i, lcp(a, b)}(a)\) 中,其中 \(lcp(a,b)\) 表示 \(a\) 和 \(b\) 的最长公共前缀。

令 \(f(i,j)=|T_{i,j}(a)|\)。

当 \(j=n\) 的时候,\(\sum \limits_{i=1}^n f(i,n)\) 显然就是 \(a\) 的循环移位的数量。因为 \(a\) 已经是一个 necklace 了,所以就是 \(\operatorname{lyn}(a)\)。

否则我们可以分两种情况讨论。

  1. \(i+j \leq n\)​。这种情况下字符串一定是形如 \(c \operatorname{pref}_j de\),其中 \(c\) 是长度为 \(i-1\) 的每个后缀都大于 \(a\) 的字符串,\(d\) 是小于 \(a_{j+1}\) 的字符,\(e\) 是长度为 \(n-i-j\) 的任意字符串。所以这种情况下

    \[f(i,j)=B_{i-1,0} \cdot (a_{j+1}-\mathrm a) \cdot |\Sigma|^{n-i-j} \]

  2. \(i+j>n\)。这种情况下字符串一定是形如 \(a_{n-i+2 \ldots j} cd \operatorname{pref}_{n-i+1}\),其中 \(c\) 是小于 \(a_{j+1}\) 的字符,\(d\) 是长为 \(n-j-1\) 的字符串。\(a_{n-i+2 \ldots j} cd\) 的每个后缀都大于 \(a\)。

    因为 \(a\) 是一个 necklace,所以肯定有 \(a_{n-i+2\ldots j} \geq \operatorname{pref}_{i+j-n-1}\)。

    令 \(s\) 为最大的满足 \(a_{j-s+1\ldots j} = \operatorname{pref}_s\),则 \(b_{t\ldots n}>a\ (t \leq i+j-n-1-s)\)​。

    我们可以根据 Lemma 1 在 \(O(n^2)\) 时间内预处理出所有的 \(s\)。

    所以直接考虑贡献:\(c = a_{s+1}\) 的贡献为 \(B_{n-j+s,s+1}\);\(c>a_{s+1}\) 的贡献为 \(B_{n-j-1,0} \cdot (a_{j+1}-a_{s+1}-1)\)​。

    \[f(i,j) = B_{n-j+s,s+1} + B_{n-j-1,0} \cdot (a_{j+1}-a_{s+1}-1) \]

因为

\[T(a) = \sum _{i=1}^n \sum _{j=0}^n f(i,j) \]

所以我们做一遍简单的求和就可以算出 \(T(a)\)​。

算法七:在 \(O(n^2)\) 时间内求出 Lyndon word \(a\) 在长度为 \(|a|\) 的 Lyndon word 中的排名。

这好像是个广为人知的结论?

\(RL(a)\) 表示 \(a\) 在同长度的 Lyndon word 中的排名。

令 \(P(a)\) 表示最小循环表示不大于 \(a\) 并且和 \(a\) 等长的不循环字符串的数量。

显然,\(RL(a) = P(a)/n\),并且 \(T(a) = \sum \limits_{d|n} P(\operatorname{pref}_d)\)。

莫比乌斯反演可得

\[P(a) = \sum _{d|n} \mu \left(\dfrac n d\right) T(\operatorname{pref}_d) \]

\[RL(a) = \dfrac 1 n \sum _{d|n} \mu \left(\dfrac n d\right) T(\operatorname{pref}_d) \]

Bonus:我们可以用 Burnside 引理得出计算 necklace 排名的方法。

代码


#pragma GCC optimize(3)
#include <bits/stdc++.h>

typedef unsigned long long ull;
const int N = 55;
int isprime[N], mu[N], prime[N], num, suf[N][N];

int cur_n;
struct modmodint { // a * n + b
    ull a; int b;
    modmodint() { a = b = 0; }
    modmodint(int _a) { a = _a / cur_n, b = _a % cur_n; }
    modmodint(ull _a, int _b) { a = _a, b = _b; }
} pow26[N][N], B[N][N];

inline modmodint operator +(const modmodint &x, const modmodint &y) {
    int tmp = x.b + y.b;
    if (tmp >= cur_n) return modmodint(x.a + y.a + 1, tmp - cur_n);
    else return modmodint(x.a + y.a, tmp);
}

inline modmodint operator -(const modmodint &x, const modmodint &y) {
    int tmp = x.b - y.b;
    if (tmp < 0) return modmodint(x.a - y.a - 1, tmp + cur_n);
    else return modmodint(x.a - y.a, tmp);
}

inline modmodint operator *(const modmodint &x, const modmodint &y) {
    modmodint z;
    z.a = x.a * y.a * cur_n + x.a * y.b + x.b * y.a;
    z.b = x.b * y.b;
    z.a += z.b / cur_n, z.b %= cur_n;
    return z;
}

inline modmodint& operator +=(modmodint &x, const modmodint &y) { return x = x + y; }
inline modmodint& operator -=(modmodint &x, const modmodint &y) { return x = x - y; }

void get_mu(int n) {
	isprime[1] = mu[1] = 1;
	for (int i = 2; i < n; ++i) {
		if (!isprime[i]) prime[++num] = i, mu[i] = -1;
		for (int j = 1; j <= num && i * prime[j] < n; ++j) {
			isprime[i * prime[j]] = 1;
			if (i % prime[j]) mu[i * prime[j]] = -mu[i];
			else { mu[i * prime[j]] = 0; break; }
		}
	}
}

int lyn(char *s, int len) {
	int p = 1;
	for (int i = 2; i <= len; ++i) {
		if (s[i] > s[i - p]) p = i;
		else if (s[i] < s[i - p]) return p;
	}
	return p;
}

bool is_necklace(char *s, int len) {
	int p = 1;
	for (int i = 2; i <= len; ++i) {
		if (s[i] > s[i - p]) p = i;
		else if (s[i] < s[i - p]) return 0;
	}
	return (len % p == 0);
}

bool get_largest_lyndon(char *t, int n) {
    while (lyn(t, n) != n) {
        int p = lyn(t, n);
        if (t[p] == 'a') return 0;
        --t[p];
        for (int i = p + 1; i <= n; ++i) t[i] = 'z';
    }
    return 1;
}

void get_largest_necklace(char *s, int len, char *t) {
	memcpy(t, s, (len + 1) * sizeof(char));
	while (!is_necklace(t, len)) {
		int p = lyn(t, len);
		--t[p];
		for (int i = p + 1; i <= len; ++i) t[i] = 'z';
	}
}

char neck[N];

modmodint get_T(char *s, int n) {
    get_largest_necklace(s, n, neck);
	B[0][0] = 1;
	for (int i = 1; i <= n; ++i) {
		B[i][i] = 0;
		for (int j = i - 1; ~j; --j)
			B[i][j] = B[i][j + 1] + B[i - j - 1][0] * ('z' - neck[j + 1]);
	}
	for (int i = 2; i <= n; ++i) {
		int u = i;
		for (int j = i; j <= n; ++j) {
			if (neck[j] > neck[j - u + 1]) u = j + 1;
			suf[i][j] = j - u + 1;
		}
	}
	modmodint res = lyn(neck, n);
	for (int i = 1; i <= n; ++i)
		for (int j = 0; j < n; ++j) {
			if (i + j <= n) res += B[i - 1][0] * (neck[j + 1] - 'a') * pow26[cur_n][n - i - j];
			else {
				int tmp = (j < n - i + 2) ? 0 : suf[n - i + 2][j];
				if (neck[j + 1] > neck[tmp + 1])
					res += B[n - j + tmp][tmp + 1] + B[n - j - 1][0] * (neck[j + 1] - neck[tmp + 1] - 1);
			}
		}
	return res;
}

ull rank_lyndon(char *s, int n) {
    cur_n = n;
	modmodint res = 0;
	for (int i = 1; i <= n; ++i)
		if (n % i == 0) {
            if (mu[n / i] == 1) res += get_T(s, i);
            else if (mu[n / i] == -1) res -= get_T(s, i);
        }
	return res.a;
}

char t1[N], t2[N];

bool cmp(char *s, char *t, int j) {
    for (int i = 1; i <= j; ++i) if (s[i] != t[i]) return 1;
    return 0;
}

ull count_prefixed_lyndon_words(char *s, int j, int n) {
    ull res = (lyn(s, j) == j) ? 1 : 0;
    for (int i = j + 1; i <= n; ++i) {
        for (int k = 1; k <= j; ++k) t1[k] = t2[k] = s[k];
        for (int k = j + 1; k <= i; ++k) t1[k] = 'a', t2[k] = 'z';
        ull r1 = get_largest_lyndon(t1, i);
        ull r2 = get_largest_lyndon(t2, i);
        if (r1 && r2 && cmp(t1, t2, i) == 0) continue;
        if (r1) r1 = rank_lyndon(t1, i);
        if (r2) r2 = rank_lyndon(t2, i);
        res += r2 - r1;
    }
    return res;
}

ull rank(char *s, int n, int m) {
    ull res = 0;
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= i; ++j) t1[j] = s[j];
        if (get_largest_lyndon(t1, i)) res += rank_lyndon(t1, i);
    }
    for (int i = n + 1; i <= m; ++i) {
        for (int j = 1; j <= n; ++j) t1[j] = s[j];
        for (int j = n + 1; j <= i; ++j) t1[j] = 'a';
        if (get_largest_lyndon(t1, i)) res += rank_lyndon(t1, i);
    }
    return res;
}

int n, m;
ull k;
char s[N];

void inc() {
    while (m && s[m] == 'z') --m;
    if (!m) { puts("-1"); exit(0); }
    ++s[m];
}

signed main() {
	get_mu(50);
    for (cur_n = 1; cur_n <= 50; ++cur_n) {
        pow26[cur_n][0] = 1;
        for (int i = 1; i <= 50; ++i) pow26[cur_n][i] = pow26[cur_n][i - 1] * 26;
    }
    scanf("%d%llu%s", &n, &k, s + 1);
    ull kk = k;
    m = strlen(s + 1);
    ull pre = rank(s, m, n);
    while (1) {
        ull calc = count_prefixed_lyndon_words(s, m, n);
        if (k > calc) {
            k -= calc;
            inc();
        } else {
            if (lyn(s, m) == m && !--k) {
                for (int i = 1; i <= m; ++i) putchar(s[i]); putchar('\n'); return 0;
            }
            s[++m] = 'a';
        }
    }
	return 0;
}

标签:word,int,题解,modmodint,Luogu5420,Lyndon,另解,necklace,operatorname
来源: https://www.cnblogs.com/zght/p/15696228.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有