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\)),显然这两个点一定是在这个区间内最后被删除,于是有
其中 \(g(i, j, k)\) 代表 \(k\) 步删完 \((i, j)\),且 \(i\) 在最后一次被删除的方案数。
显然此时 \(j\) 一定和 \(mid\) 一起被删除,另外只有可能和 \((mid, j)\) 中同色的一起被删除,所以这样计算是不重不漏的。
那么考虑 \(j\) 单独被删除的转移
那么考虑 \(g(i, j, k)\) 的转移,我们依旧考察 \(j\) 和谁被一起删除。若 \(j \neq mid\),有
若 \(j\) 被单独删除则有
答案是 \(\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;
}

浙公网安备 33010602011771号