给定一个 N 和 K ,问所有长度为 L 的仅包含字符 a,b 的单词中有多少个无界单词,以及字典序第 K 的无界单词是什么?
有界单词:对于一个单词 S ,如果存在一个长度 L ,满足 0<L<length(S) ,并且使得 S 长度为 L 的前缀与S长度为 L 的后缀相同,则称 S 是有界的。
无界单词:对于一个单词 S ,如果不存在一个 L 使之成为有界单词,即它为无界单词。
题目有 T 组测试数据。
L<=64 , T<=4
相比第二问来说,肯定是第一问比较好处理,那我们先来考虑一下第一问。
我们可以令 Fi 表示长度为 i 的单词有多少个是无序的,计算时无界单词考虑起来比较繁琐,所以可以用所有单词的数量减去有界单词的数量来得到 Fi 的值。那么现在就需要考虑长度为 i 的有界单词的数量。
假如当前的有界单词长度为 L 的前缀和后缀相等,我们对每个单词统计数量是只考虑最小的 L 来保证不会算重。一个显然的性质就是当 L>i2 时,肯定存在一个更小的 L 使之满足要求,所以就只需考虑小于等于 i2 的 L 。考虑最小的 L 时我们可以直接把之前统计出的长度小于 i2 的无界单词复制一遍放在头和尾,中间剩下字符任选,这样就可以推出 Fi 。总的来说就可以写出递推式 Fi=2i−∑i2j=1Fj∗2i−2j 。 FN 就是第一问的答案,并且与第二问也有关联。
对于第二问,容易想到的是我们可以一位一位的判断能不能放 a ,现在的问题就变成了如何统计一个确定前 Len 个字符的字符串能构成无界单词的个数。其实跟第一问差不多,也要维护一个 Fi 表示前 i 个字符组成的字符串(包括未确定的)组成的无序单词的数量。但是递推时需要分几种情况讨论一下。
假设当前已经确定了前 Len 个字符,现在递推到第 i 位,枚举到长度为 j 的无序前缀(这里的 i 和 j 和上面的含义一样)
i≤Len
那么当前要求的前 Len 个字符都是已经确定。所以直接用 KMP 判断一下已经确定的前 i 个字符组成的单词是否是无序单词就可以了。
Len≤j
那么对当前的 Fi 造成影响的 Fj 都在前面的转移中特殊考虑过了,所以第一问一样直接转移就可以了。
Len>j , Len≤i−j
这种情况跟第二种差不多,但是相同的前后缀中有些任选的位置已经在被确定了,所以只需把中间任选的方案( 2i−2j ),改成除了确定的位置任选( 2i−2j−(Len−j) )
Len>i−j
当出现这种情况时不仅中间任选字符已经确定,而且我们把前缀复制到后缀时,放后缀的有些位置的字符也已经确定。所以我们只需用 Hash 判断一下当前超出的位置是不是当前单词的前缀。而由于可以任选字符的位置已经确定,所以如果匹配成功,只需把有序单词的个数加 +E 。
//YxuanwKeith
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef unsigned long long LL;
const int MAXN = 65, HNum = 15731;
LL K, Pow[MAXN], F[MAXN], Has[MAXN], H[MAXN];
int Len, Last, Next[MAXN], N, Pj, Ans[MAXN];
void Prepare() {
Pow[0] = Has[0] = 1;
for (int i = 1; i <= 64; i ++) Pow[i] = Pow[i - 1] * 2;
for (int i = 1; i <= 64; i ++) Has[i] = Has[i - 1] * HNum;
}
bool Judge(int Len, int r) {
int l = r - Len + 1;
return H[Len] - H[0] * Has[Len] == H[r] - H[l - 1] * Has[r - l + 1];
}
LL Solve() {
memset(F, 0, sizeof F);
for (int i = 1; i <= N; i ++) {
if (i < Len && !Next[i]) F[i] = 1;
if (i < Len) continue;
F[i] = Pow[i - Len];
for (int j = 1; j <= i / 2; j ++) {
LL Cnt;
if (Len <= j) Cnt = Pow[i - 2 * j];
if (Len > j && Len <= i - j) Cnt = Pow[i - 2 * j - (Len - j)];
if (Len > i - j) Cnt = Judge(Len - (i - j), Len);
F[i] -= F[j] * Cnt;
}
}
return F[N];
}
void PushIn(int c) {
Ans[++ Len] = c, H[Len] = H[Len - 1] * HNum + c;
if (Len == 1) return;
for (; Pj && Ans[Pj + 1] != Ans[Len]; Pj = Next[Pj]);
if (Ans[Pj + 1] == Ans[Len]) Pj ++;
Next[Len] = Pj;
}
void Work() {
Len = 0, Pj = 0;
scanf("%d%lld", &N, &K);
printf("%lld\n", Solve());
for (int i = 1; i <= N; i ++) {
Last = Pj;
PushIn(0);
LL Num = Solve();
if (Num < K) {
Pj = Last, Len --;
K -= Num;
PushIn(1);
}
}
for (int i = 1; i <= N; i ++) printf("%c", Ans[i] + 'a');
printf("\n");
}
int main() {
Prepare();
int Test;
scanf("%d", &Test);
for (; Test; Test --) Work();
}