P5770题解
第一问
对于第一问,我们可以打表找规律。我们先用状压枚举长度为
于是我们可以先预处理出来
第二问
第二问稍有复杂,需要仔细分析。
首先,要知道一点,如果一个字符串有一个长度超过该字符串长度一半的公共前后缀,则一定存在一个更短的公共前后缀,详见下图。
如图中的米黄色部分就是新的公共前后缀。
由此可知,对于每一个不满足要求的字符串,都有一个唯一确定的最短公共前后缀。
再看题目要求,他要问第 a,然后算出这种情况下的无界单词的数量 a,否则就确定为 b,并更新排名 k -= o,表示该位填 a 一定比填 b 的字典序小。
那么怎么计算在确定了前
具体的统计方法就是确定了前后缀的长度和个数后,剩下的位置随便填。计算的时候需要注意有的地方的值已经确定下来了,不能随便填。或者是有的前缀在填成后缀后可能会与已经确定的字符串区间重合,需要特判一下。
具体细节看代码。
代码
提交之后直接刷紫,拿下最优解。
#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;
}