CF1089I & P7278

CF1089I#

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

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

正难则反,考虑反着做。

  1. 根是析点

考虑枚举根的儿子数目与排列方式。前者是经典划分数 dp,si,jsik,j1k!。后者是一个子问题,可设 fn 为析点的 n 个子树的排列数,其中并不包含非平凡连续段。答案为 i=4nsn,ifi

  1. 根是合点

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

gn 为如此的方案数:i[2,n1]g[1,i] 为一个连续段。有 gn=n!i=1n1gi(ni)!。此时的方案数即为 2(n!gn)

析合树好抽象。因为不大了解它的结构,甚至不会自己设计 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 拥有上限。根是合点时,最长的连续段是去掉第一个连续段或者去掉最后一个连续段得到的,因此这两个连续段长度不超过 nm。枚举这两段的长度,中间可以随便排。

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);
}

作者:purplevine

出处:https://www.cnblogs.com/purplevine/p/17999759

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   purplevine  阅读(24)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示