[JSOI2016] 无界单词

[JSOI2016] 无界单词

一道普通的 DP .

首先我们很容易可以知道, 一个串的最短的 border 的长度一定 串长的一半.

证明很简单, 如果有一个长度 一半的 border 那么这个 border 的前后有重叠, 重叠段就是一个更短的 border .

同理我们也可以得到, 最短 border 一定是没有 border 的.

然后暴力枚举最短 border 的长度.

我们设 f[i] 表示长度为 i 的无界单词的个数, 则转移: f[i]=2ij=1i2f[j]2i2j

复杂度 O(N2) .

第一问就完成了.

接下来考虑第二问.

我们需要输出一个串, 使得它为字典序为第 k 大的无界单词.

这里我们试填, 假设当前位为 a 然后求出最大的字典序是多少, 如果 k , 则当前位为 a , 否则为 b .

然后怎么求最大的字典序呢? 其实就是填完这几位后没有 border 的方案数.

还是设 f[i] 表示长度为 i 的无界单词数, 当前枚举到第 l 位, 显然 f[i],i[1,l] 只能为 0/1 , f[i]=2il,i[l+1,n]. 然后像第一问一样 DP 就行了, 只不过需要分类讨论, 枚举 i 表示串长, j 表示最短的 border 长.

  1. jl , 跟第一问一样, 确定了前后 j 位, 然后剩下的 i2j 位随便选. f[i]=f[j]2i2j
  2. jl,jil , 这样的话, 前后缀出现重叠, 我们就比较一下重叠的部分, 如果相同, 就 =f[j] , 否则说明当前不存在长度为 jborder .
  3. jl,jil , 同样的, 确定了前面 l 位以及后面 j 位, 剩下的随便选. f[i]=f[j]2ilj

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  阅读(41)  评论(0)    收藏  举报
编辑推荐:
· golang中写个字符串遍历谁不会?且看我如何提升 50 倍
· C# 代码如何影响 CPU 缓存速度?
· 智能桌面机器人:使用 .NET 为树莓派开发 Wifi 配网功能
· C# 模式匹配全解:原理、用法与易错点
· 记一次SSD性能瓶颈排查之路——寿命与性能之间的取舍
阅读排行:
· 时隔半年,拾笔分享:来自一个大龄程序员的迷茫自问
· 《程序员的底层思维》读后感
· 曾经风光无限的 Oracle DBA 已经落伍了吗?
· 不写一行代码 .NET 使用 FluentCMS 快速构建现代化内容管理系统(CMS)
· WineHQ 发布的 Framework Mono 6.14 的这个特性对Windows Form
点击右上角即可分享
微信分享提示