[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\) 长.

  1. \(j \ge l\) , 跟第一问一样, 确定了前后 \(j\) 位, 然后剩下的 \(i - 2j\) 位随便选. \(f[i] -= f[j] * 2^{i - 2j}\)
  2. \(j \le l, j \ge i - l\) , 这样的话, 前后缀出现重叠, 我们就比较一下重叠的部分, 如果相同, 就 \(-= f[j]\) , 否则说明当前不存在长度为 \(j\)\(border\) .
  3. \(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;
}

这个题画图会好理解很多, 所以有时候做题没思路可以在纸上画画图ヾ(✿゚▽゚)ノ.

posted @ 2021-09-16 16:21  sshadows  阅读(30)  评论(0编辑  收藏  举报