Loading

CF1089I & P7278

CF1089I

看到连续段,考虑上析合树。

因为不存在长度为 \([2, n-1]\) 的连续段,根是析点,它有 \(n\) 个长度为 \(1\) 的儿子。

正难则反,考虑反着做。

  1. 根是析点

考虑枚举根的儿子数目与排列方式。前者是经典划分数 dp,\(s_{i, j} \gets \sum s_{i-k, j-1} \cdot k!\)。后者是一个子问题,可设 \(f_n\) 为析点的 \(n\) 个子树的排列数,其中并不包含非平凡连续段。答案为 \(\sum_{i=4}^{n} s_{n, i} f_i\)

  1. 根是合点

不妨假设根的儿子升序排列,枚举第一个儿子的长度即可。

\(g_n\) 为如此的方案数:\(\not\exist i \in [2, n-1]\)\(g_{[1, i]}\) 为一个连续段。有 \(g_n = n!-\sum_{i=1}^{n-1} g_i(n-i)!\)。此时的方案数即为 \(2(n!-g_n)\)

析合树好抽象。因为不大了解它的结构,甚至不会自己设计 dp 状态了。但是说到底又看着好简单啊,唉。

int main() {
  int n = 400, q; scanf("%d %d", &q, &mod);
  initC(n + 1);
  std::vector<modint> f(n + 1), g(n + 1);
  std::vector<std::vector<modint>> s(n + 1, std::vector<modint>(n + 1));
  s[0][0] = 1;
  f[1] = 1, f[2] = 2;
  for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= i; j++)
      for (int k = 1; k <= i; k++) 
        s[i][j] += s[i - k][j - 1] * fac[k];
    g[i] = fac[i];
    for (int j = 1; j <= i - 1; j++)
      g[i] -= g[j] * fac[i - j];
    if (i < 4) continue;
    f[i] = fac[i];
    for (int j = 4; j <= i - 1; j++) f[i] -= s[i][j] * f[j];
    f[i] -= 2 * (fac[i] - g[i]);
  }
  while (q--) {
    int x; scanf("%d", &x); printf("%d\n", f[x]);
  }
}

P7278

考虑有什么地方要改过来。

析点时的划分,\(k\) 拥有上限。根是合点时,最长的连续段是去掉第一个连续段或者去掉最后一个连续段得到的,因此这两个连续段长度不超过 \(n-m\)。枚举这两段的长度,中间可以随便排。

int main() {
  int n, m; scanf("%d %d", &n, &m);
  initC(n);
  std::vector<std::vector<modint>> h(n + 1, std::vector<modint>(n + 1));
  h[0][0] = 1;
  for (int i = 1; i <= n; i++) 
    for (int j = 1; j <= n; j++) 
      for (int k = 1; k <= i && k <= m; k++)
        h[i][j] += h[i - k][j - 1] * fac[k];
  std::vector<modint> f(n + 1), g(n + 1);
  std::vector<std::vector<modint>> s(n + 1, std::vector<modint>(n + 1));
  s[0][0] = 1;
  f[1] = 1, f[2] = 2;
  for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= i; j++)
      for (int k = 1; k <= i; k++) 
        s[i][j] += s[i - k][j - 1] * fac[k];
    g[i] = fac[i];
    for (int j = 1; j <= i - 1; j++)
      g[i] -= g[j] * fac[i - j];
    if (i < 4) continue;
    f[i] = fac[i];
    for (int j = 4; j <= i - 1; j++) f[i] -= s[i][j] * f[j];
    f[i] -= 2 * (fac[i] - g[i]);
  }
  modint ans = fac[n];
  for (int i = n - m; i <= m; i++)
    for (int j = n - m; j <= m && j <= n - i; j++)
      ans -= 2 * g[i] * g[j] * fac[n - i - j];
  for (int i = 4; i <= n; i++)
    ans -= h[n][i] * f[i];
  printf("%d\n", ans);
}
posted @ 2024-01-31 17:31  purplevine  阅读(20)  评论(0编辑  收藏  举报