1 条题解
-
0
自动搬运
来自洛谷,原作者为

Richard_H
法 小 师搬运于
2025-08-24 22:14:14,当前版本为作者最后更新于2023-11-11 00:13:45,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
第一问
对于第一问,我们可以打表找规律。我们先用状压枚举长度为 的单词,然后用 KMP 判断他是否存在公共前后缀。当然,这个规律事实上不是我看出来的,而是 这位大佬 一眼瞄出来的。打表代码还算好写,在这里就不放了(绝对不是因为不小心已经删掉了),我们令长度为 的无界单词的数量为 的话就有这样的规律。
$$p_i = \begin{cases} p_{i - 1} * 2 & i 为奇数 \\ p_{i - 1} * 2 - p_{\frac i2} & i 为偶数 \end{cases} $$于是我们可以先预处理出来 所有的答案,然后每次询问的第一行直接输出即可。
第二问
第二问稍有复杂,需要仔细分析。
首先,要知道一点,如果一个字符串有一个长度超过该字符串长度一半的公共前后缀,则一定存在一个更短的公共前后缀,详见下图。

如图中的米黄色部分就是新的公共前后缀。
由此可知,对于每一个不满足要求的字符串,都有一个唯一确定的最短公共前后缀。
再看题目要求,他要问第 小的,那么我们结合 Trie 树的思想,从高往低确定每一位是什么。在计算第 位时,我们肯定是已经确定了前 位的,并且知道我们要的答案字符串在确定前 位后的排名 ,所以我们可以先假设第 位是
a,然后算出这种情况下的无界单词的数量 (变量名稍稍有点奇怪),如果 在 内,那么第 位就确定是a,否则就确定为b,并更新排名k -= o,表示该位填a一定比填b的字典序小。那么怎么计算在确定了前 位后有多少种无界单词呢?这个需要用到容斥原理。用 表示当前情况下长度为 的字符串中有多少个无界单词。如果 ,那么显然可以通过 KMP 算公共前后缀直接得出来。如果 则需要总方案数减去有公共前后缀的方案数。回想一下上面的结论,如果一个字符串的公共前后缀是最短的,那么他长度一定不超过该字符串长度的一半。而且,如果这个公共前后缀是最短的,那这个公共前后缀自己也一定是无界单词(如果不是的话就取前缀的前缀和后缀的后缀会找到更短的)。所以我们可以枚举该字符串的前缀,来计算需要减掉的方案数。
具体的统计方法就是确定了前后缀的长度和个数后,剩下的位置随便填。计算的时候需要注意有的地方的值已经确定下来了,不能随便填。或者是有的前缀在填成后缀后可能会与已经确定的字符串区间重合,需要特判一下。
具体细节看代码。
代码
提交之后直接刷紫,拿下最优解。
#include<iostream> #include<string.h> using namespace std; typedef long long lol; typedef unsigned long long ull; const int N = 65; int T, n, c[N], nxt[N]; ull p[N], k, f[N]; // 更新 i 位置的 nxt[] 数组 void KMP (int &j, int i) { while (j && c[j + 1] != c[i]) j = nxt[j]; if (i > 1 && c[j + 1] == c[i]) j ++ ; } bool st[N]; ull ahead (int i) { memset (st, 0, sizeof (st)); int j = i; while (j) st[j] = true, j = nxt[j]; for (int j (i + 1); j <= (n >> 1); ++ j ) { f[j] = 1ull << (j - i); for (int t (1); t <= (j >> 1); ++ t ) if (f[t] && (t <= j - i || st[t - j + i])) f[j] -= f[t] * (1ll << max (0, j - max (i, t) - t)); } ull ret = 1ull << (n - i); for (int j (1); j <= (n >> 1); ++ j ) if (j <= n - i || st[j - n + i]) ret -= f[j] * (1ull << max(0, n - max (i, j) - j)); return ret; } int main () { // 预处理第一问答案 p[0] = 1; for (int i = 1; i <= 64; ++ i ) if (i & 1) p[i] = p[i - 1] << 1; else p[i] = (p[i - 1] << 1) - p[i >> 1]; scanf ("%d", &T); while (T -- ) { scanf ("%d%llu", &n, &k); printf ("%llu\n", p[n]); if (n == 1) { putchar (k + 'a' - 1), putchar ('\n'); continue; } // 最后一位可以直接通过第一位得到,不用循环到底 for (int i (1), j (0); i < n; ++ i ) { // 假设第 i 的位置上是 a c[i] = 0; int t = j; KMP (j, i), nxt[i] = j, f[i] = !j; ull o = ahead (i); if (o < k) { k -= o; // 修改为 b c[i] = 1; j = t; KMP (j, i), f[i] = !j, nxt[i] = j; } } c[n] = !c[1]; for (int i (1); i <= n; ++ i ) putchar ('a' + c[i]); putchar ('\n'); } return 0; }
- 1
信息
- ID
- 4782
- 时间
- 1000ms
- 内存
- 500MiB
- 难度
- 5
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者