CTSC2016 香山的树

CTSC2016 香山的树

题意

给定字符串 \(S\),求字典序大于等于 \(S\) ,长度小于等于 \(n\) 的 Lyndon 串中第 \(k\) 小的串。

\(|S| \le 50, |\Sigma| = 26, k \le 10^{15}\)

题解

计数 Lyndon 串

定义一个串 \(s\) 是循环串当且仅当存在 \(t^n = s\space (n > 1)\)

如果没有字典序的限制,可以注意到如果一个串 \(s\) 不是循环串,那么这个串的最小起点是唯一的。

不存在非平凡整周期的串数可以通过将 \(f(n) = |\Sigma|^n\)\(\mu\) 做 Dirichlet 卷积得到。

用 Trie 树限制字典序

对于字典序的限制,考虑类似于线段树上二分的过程,我们在 Trie 上递归解决问题(实现时注意一个非叶子的 Lyndon 串节点对答案可以产生贡献)

问题转化成求 Trie 上一个子树的大小,即以某个串为前缀,长度不超过 \(n\) 的 Lyndon 串数。

计数以 \(p\) 为前缀的 Lyndon 串数

设当前考虑的前缀为 \(p\)。对于一个符合要求的串 \(s\),其 所有子串 满足 \(s < p\)\(s\)\(p\) 的前缀。

这个限制可以通过对 \(p\) 预处理 KMP 自动机处理。如果一个节点的一条出边(的字符)小于这个节点的另一条不指向根节点的出边(即可以匹配 \(s\))的字符,则这条出边需要删除。

仍然假设 \(\boldsymbol{s}\) 不是循环串,则将 \(s\) 无限重复后,选择的最小起始位置是唯一的,且是所有 \(p\) 出现的 起始位置 中的一个。

\(F(L, node, cnt)\) 表示长度为 \(L\),在 KMP 自动机上匹配的节点为 \(node\),且已经匹配 \(s\) 次数 \(cnt\)。那么一个次数 \(cnt\) 的方案对答案的贡献为 \(1 / cnt\)。最后需要在串尾加上 \(s\) (除最后一个字符外)来解决匹配 \(p\) 的末尾超过 \(s\) 的情形。 转移枚举下一个字符即可。

考虑去除循环串的方案。记 \(ans_i\) 表示长度为 \(i\) ,且无限重复后以 \(p\) 为前缀的串数。对于 \(i \le |p|\)\(ans_i = 1\) 当且仅当 \(i\) 前缀是 Lyndon 串且 \(i\)\(p\) 的周期,否则为 \(0\)

对于 \(i > |p|\),可以先用 DP 数组的值求出不考虑循环串的答案。注意到对于 \(t^c = s\)\(t\) 对于 \(ans_{|s|}\) 的贡献为 \(1 / c\)(注意到本质相同的起点只被计入一次贡献,且这个系数对于 \(|t| \le |p|\)\(|t| > p\) 都成立),那么按照这个系数去除循环串贡献即可。


注意到 KMP 自动机上每个点可以转移到的节点只有 \(2\) 种,那么可以 \(\mathcal O(1)\) 转移,DP 一次的复杂度为 \(\mathcal O(|p|^3)\),共访问 \(\mathcal O(n|\Sigma|)\) 个 Trie 树节点,那么复杂度 \(\mathcal O(n^4 |\Sigma|)\)

状态存储可以直接使用 long double\(64\) bit 有效精度可以满足 \(10^{15}\) 的精度要求,实测 double 也可以通过。

实现

可能具体细节和上面不完全相同,也比较乱。

#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#include <numeric>
#include <vector>
#include <cassert>
#include <cmath>
using namespace std;

#define LOG(f...) fprintf(stderr, f)

using ll = long long;

template<class T> void read(T &x) {
  char ch; x = 0;
  int f = 1;
  while (isspace(ch = getchar()));
  if (ch == '-') ch = getchar(), f = -1;
  do x = x * 10 + (ch - '0'); while(isdigit(ch = getchar()));
  x *= f;
}
template<class T, class ...A> void read(T &x, A&... args) { read(x); read(args...); }

using fp = long double;

const int N = 51;

char s[N], ans[N];
int kmp[N][26], fa[N];
int kmp_valued[N], kmp_c0[N];
fp dp[N][N][N], res_len[N];
int n;
ll k;

bool is_lyndon(char *s, int len = -1) {
  if (len == -1) len = strlen(s + 1);
  for (int i = 2; i <= len; ++i) {
    for (int j = 0; j <= len - i; ++j) {
      if (s[i + j] < s[1 + j]) return false;
      if (s[i + j] > s[1 + j]) goto correct;
    }
    return false;
    correct:;
  }
  return true;
}

fp DP(char *s) {
  memset(dp, 0, sizeof(dp));
  memset(res_len, 0, sizeof(res_len));
  int len = strlen(s + 1);
  if (len == n)
    return is_lyndon(s);
  for (int i = 2; i <= len; ++i) {
    for (int j = 0; j <= len - i; ++j)
      if (s[i + j] > s[1 + j]) break;
      else if (s[i + j] < s[1 + j]) return 0;
  }
  fa[1] = 0;
  kmp[0][s[1] - 'a'] = 1;
  for (int i = 0; i != s[1] - 'a'; ++i)
    kmp[0][i] = -1;
  for (int i = 1; i <= len; ++i) {
    memcpy(kmp[i], kmp[fa[i]], sizeof(kmp[0]));
    if (i != len) {
      kmp[i][s[i + 1] - 'a'] = i + 1;
      fa[i + 1] = kmp[fa[i]][s[i + 1] - 'a'];
    }
    for (int j = 25; j > 0; --j)
      if (kmp[i][j]) {
        for (int k = 0; k != j; ++k)
          kmp[i][k] = -1;
        break;
      }
  }
  memset(kmp_valued, -1, sizeof(kmp_valued));
  memset(kmp_c0, 0, sizeof(kmp_c0));
  for (int i = 0; i <= n; ++i)
    for (int j = 0; j < 26; ++j)
      if (kmp[i][j] == 0) ++kmp_c0[i];
      else if (kmp[i][j] > 0) kmp_valued[i] = kmp[i][j];

  dp[0][len][0] = 1;
  for (int i = 0; i < n - len; ++i) {
    for (int j = 0; j <= len; ++j) {
      for (int k = 0; k <= i; ++k) {
        if (kmp_valued[j] != -1)
          dp[i + 1][kmp_valued[j]][k + (kmp_valued[j] == len)] += dp[i][j][k];
        dp[i + 1][0][k] += dp[i][j][k] * kmp_c0[j];
      }
    }
  }
  for (int j = 0; j <= len; ++j) {
    int u = j;
    bool flag = true;
    int cnt = 0;
    for (int i = 1; i <= len; ++i) {
      if (kmp[u][s[i] - 'a'] == -1) { flag = false; break; }
      u = kmp[u][s[i] - 'a'];
      if (u == len) ++cnt;
    }
    if (!flag) continue;
    for (int k = 0; k <= n - len; ++k) {
      if (k + cnt == 0) continue;
      fp val = fp(1) / (k + cnt);
      for (int i = k; i <= n - len; ++i)
        res_len[i + len] += val * dp[i][j][k];
    }
  }
  for (int i = 1; i <= len; ++i) {
    bool flag = true;
    for (int j = i + 1; j <= len; ++j)
      if (s[j] != s[j - i]) { flag = false; break; }
    res_len[i] = flag && is_lyndon(s, i);
  }
  for (int i = len + 1; i <= n; ++i) {
    for (int j = 1, li = i / 2; j <= li; ++j)
      if (i % j == 0)
        res_len[i] -= res_len[j] * (fp(j) / i);
  }
  return accumulate(res_len + len, res_len + n + 1, fp(0));
}

int len_s;

ll solve(int p, ll cnt, bool greater) {
  ans[p] = 0;
  ll orig_cnt = cnt;
  if (!s[p] || greater) {
    fp val = DP(ans);
    if (val + 0.5 < cnt) return ll(val + 0.5);
    if (fabsl(val - cnt) < 0.5 && p == n + 1)
      puts(ans + 1), exit(0);
  }
  cnt -= (greater || p > len_s) && is_lyndon(ans);
  if (!cnt) {
    puts(ans + 1);
    exit(0);
  }
  for (ans[p] = max('a', greater ? 'a' : s[p]); ans[p] <= 'z'; ++ans[p]) {
    fp val = solve(p + 1, cnt, greater || (ans[p] > s[p]));
    cnt -= ll(val + 0.5);
  }
  ans[p] = 0;
  return orig_cnt - cnt;
}

int main() {
#ifndef ONLINE_JUDGE
  freopen("input.txt", "r", stdin);
  freopen("output.txt", "w", stdout);
#endif
  scanf("%d%lld", &n, &k);
  scanf("%s", s + 1);
  len_s = strlen(s + 1);
  solve(1, k, false);
  puts("-1");
  return 0;
}
posted @ 2021-06-04 20:53  RiverHamster  阅读(211)  评论(0编辑  收藏  举报
\