Loading

P10202 [湖北省选模拟 2024] 沉玉谷 / jade 解题报告

区间 DP 超值全家桶。

题意就是,有一个数组,每次删去一段只有一种颜色的区间,剩下的拼接起来(重新编号),求所不同的 \([l, r]\) 序列个数, \(n \le 50\)

首先显然一种 \([l,r]\) 序列只能对应一种操作方案,这个要提前想好。然后想办法求操作方案数。

我会 DP!

对于 \(n \le 18\) 的情况,直接状压 DP,记 \(f(i)\) 代表子集 \(i\) 有多少种删法,实现精细一点可以做到 \(\mathcal{O}(n^22^n)\),有 \(8\) 分。

我会注意!

\(a_i = i\) 时,只能一个一个删,于是方案数是 \(n!\)。你又可以拿到 \(4\) 分。

我还会 DP!

\(a_i = 1\) 时,颜色限制形同虚设,直接令 \(f_i\) 代表删掉 \(i\)\(1\) 的方案数,打表 DP 均可快速求出,你又有 \(4\) 分了。

我会正解!

这个特征很明显。没什么好说的,直接考虑区间 DP,记 \(f(i, j)\) 代表删完 \([i, j]\) 方案数。

这里,一个比较套路的计数方法就是:枚举 \(j\) 和区间内的哪个同色元素一起被删除(广义上来说其实是枚举区间端点的状态)。我们其实需要对两个区间的操作进行插板法合并,所以我们需要再当前的状态上再加一维 \(f(i, j, k)\),代表 \(k\) 步删完。考虑如果 \(j\)\(mid\) 一起被删的话(\(j \neq mid\)),显然这两个点一定是在这个区间内最后被删除,于是有

\[f(i, j, k) \stackrel{+}{\longleftarrow} f(i, mid - 1, x) \cdot g(mid, j - 1, k - x) \cdot \binom{k}{x} \]

其中 \(g(i, j, k)\) 代表 \(k\) 步删完 \((i, j)\),且 \(i\) 在最后一次被删除的方案数。

显然此时 \(j\) 一定和 \(mid\) 一起被删除,另外只有可能和 \((mid, j)\) 中同色的一起被删除,所以这样计算是不重不漏的。

那么考虑 \(j\) 单独被删除的转移

\[f(i, j, k) \stackrel{+}{\longleftarrow} f(i, j - 1, k - 1) \cdot k \]

那么考虑 \(g(i, j, k)\) 的转移,我们依旧考察 \(j\) 和谁被一起删除。若 \(j \neq mid\),有

\[g(i, j, k) \stackrel{+}{\longleftarrow} g(i, mid - 1, x) \cdot g(mid, j - 1) \cdot \binom{k - 1}{x - 1} \]

\(j\) 被单独删除则有

\[g(i, j, k) \stackrel{+}{\longleftarrow} g(i, j - 1, k - 1) \cdot (k - 1) \]

答案是 \(\sum f(1, n, *)\)。如果这题的时间瓶颈不在 \(g\) 的计算,我们还可以将 \(f\) 优化为 \(f(i, j)\) 代表删除前 \(i\) 个的方案数,这里没必要了。

时间复杂度 \(\mathcal{O}(n^5)\),做完了。

一遍过了我去。

#include <bits/stdc++.h>
#define int long long
#define Misaka namespace
#define Network std
using Misaka Network;

const int N = 57, MOD = 1e9 + 7;
int f[N][N][N], g[N][N][N], a[N], n, c[N][N];

signed main(){
  ios::sync_with_stdio(0), cin.tie(0);
  
  cin >> n;
  c[0][0] = 1;
  for(int i = 1; i <= n; i ++){
    for(int j = 0; j <= i; j ++){
      (c[i][j] = (j == 0 ? 0 : c[i - 1][j - 1]) + c[i - 1][j]) %= MOD;
    }
  }
  for(int i = 1; i <= n; i ++) cin >> a[i];

  for(int i = 1; i <= n; i ++){
    for(int j = i; j >= 1; j --){
      f[j][i][1] = g[j][i][1] = 1;
      if(a[j - 1] != a[j]) break;
    }
  }

  for(int len = 2; len <= n; len ++){
    for(int l = 1, r = len; r <= n; l ++, r ++){
      for(int k = 2; k <= n; k ++){
        (f[l][r][k] += f[l][r - 1][k - 1] * k) %= MOD;
        if(a[l] == a[r]) (f[l][r][k] += g[l][r - 1][k]) %= MOD;
        for(int mid = l + 1; mid < r; mid ++){
          for(int x = 1; x < k; x ++) if(a[mid] == a[r])
            (f[l][r][k] += f[l][mid - 1][x] * g[mid][r - 1][k - x] % MOD * c[k][x]) %= MOD;
        }
        (g[l][r][k] += g[l][r - 1][k - 1] * (k - 1)) %= MOD;
        if(a[l] == a[r]) (g[l][r][k] += g[l][r - 1][k]) %= MOD;
        for(int mid = l + 1; mid < r; mid ++){
          for(int x = 1; x < k; x ++) if(a[mid] == a[r])
            (g[l][r][k] += g[l][mid - 1][x] * g[mid][r - 1][k - x] % MOD * c[k - 1][x - 1]) %= MOD;
        }
      }
    }
  }

  int ans = 0;
  for(int i = 1; i <= n; i ++) (ans += f[1][n][i]) %= MOD;
  cout << ans << "\n";
  
  return 0;
}
posted @ 2026-08-26 21:22  Trent900  阅读(14)  评论(0)    收藏  举报