[JSOI2016] 无界单词
[JSOI2016] 无界单词
一道普通的 \(DP\) .
首先我们很容易可以知道, 一个串的最短的 \(border\) 的长度一定 \(\le\) 串长的一半.
证明很简单, 如果有一个长度 \(\ge\) 一半的 \(border\) 那么这个 \(border\) 的前后有重叠, 重叠段就是一个更短的 \(border\) .
同理我们也可以得到, 最短 \(border\) 一定是没有 \(border\) 的.
然后暴力枚举最短 \(border\) 的长度.
我们设 \(f[i]\) 表示长度为 \(i\) 的无界单词的个数, 则转移: \(f[i] = 2^i - \displaystyle\sum_{j = 1}^{\lfloor \frac{i}{2} \rfloor} f[j] * 2^{i - 2j}\)
复杂度 \(O(N^2)\) .
第一问就完成了.
接下来考虑第二问.
我们需要输出一个串, 使得它为字典序为第 \(k\) 大的无界单词.
这里我们试填, 假设当前位为 \(a\) 然后求出最大的字典序是多少, 如果 \(\ge k\) , 则当前位为 \(a\) , 否则为 \(b\) .
然后怎么求最大的字典序呢? 其实就是填完这几位后没有 \(border\) 的方案数.
还是设 \(f[i]\) 表示长度为 \(i\) 的无界单词数, 当前枚举到第 \(l\) 位, 显然 \(f[i], i \in [1, l]\) 只能为 \(0/1\) , \(f[i] = 2^{i - l}, i \in [l + 1, n]\). 然后像第一问一样 \(DP\) 就行了, 只不过需要分类讨论, 枚举 \(i\) 表示串长, \(j\) 表示最短的 \(border\) 长.
- \(j \ge l\) , 跟第一问一样, 确定了前后 \(j\) 位, 然后剩下的 \(i - 2j\) 位随便选. \(f[i] -= f[j] * 2^{i - 2j}\)
- \(j \le l, j \ge i - l\) , 这样的话, 前后缀出现重叠, 我们就比较一下重叠的部分, 如果相同, 就 \(-= f[j]\) , 否则说明当前不存在长度为 \(j\) 的 \(border\) .
- \(j \le l, j \le i - l\) , 同样的, 确定了前面 \(l\) 位以及后面 \(j\) 位, 剩下的随便选. \(f[i] -= f[j] * 2^{i - l - j}\)
\(code:\)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
ull read() {
ull x = 0, f = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-') f = -1;
ch = getchar();
}
while (isdigit(ch)) {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
const int N = 100;
ull f[N], pw[N];
char s[N];
bool check(int n) {
for (int l = 1; l <= n >> 1; l++) {
int flag = 1;
for (int i = 1; i <= l; i++) {
if (s[i] != s[n - l + i]) {
flag = 0;
break;
}
}
if (flag) return false;
}
return true;
}
void solve() {
int n = read();
ull k = read();
for (int i = 1; i <= n; i++) f[i] = pw[i];
for (int i = 1; i <= n; i++)
for (int j = 1; j <= i >> 1; j++)
f[i] -= f[j] * pw[i - (j << 1)];
if (n == 64) f[n]++;
printf("%llu\n", f[n]);
for (int l = 1; l <= n; l++) {
s[l] = 'a';
for (int i = 1; i <= l; i++) f[i] = check(i);
for (int i = l + 1; i <= n; i++) {
f[i] = pw[i - l];
for (int j = 1; j <= i >> 1; j++) {
if (j >= l) f[i] -= f[j] * pw[i - (j << 1)];
else if (j >= i - l) {
for (int a = i - j + 1, b = 1; a <= l; a++, b++) {
if (s[a] != s[b]) {
f[i] += f[j];
break;
}
}
f[i] -= f[j];
}
else f[i] -= f[j] * pw[i - l - j];
}
}
if (f[n] < k) {
k -= f[n];
s[l] = 'b';
}
}
for (int i = 1; i <= n; i++) printf("%c", s[i]);
printf("\n");
}
int main() {
for (int i = 0; i < 64; i++) pw[i] = 1ull << i, pw[64] += pw[i];
int T = read();
while (T--) solve();
return 0;
}
这个题画图会好理解很多, 所以有时候做题没思路可以在纸上画画图ヾ(✿゚▽゚)ノ.