ABC313H
标签
插入 DP先考虑如果给了一个 \(a\),排列 \(b\) 的最优策略是啥?
其实就是令 \(c_i = \min(a_i, a_{i - 1})(a_0 = a_{n + 1} = -\infty)\) ,然后 \(c_i, b_i\) 都从大到小排序,所有 \(c_i < b_i\) 即可。
这提示我们要按 \(c, b\) 从大到小的顺序考虑。于是我们将 \(a, b\) 都从大到小排序,依次插入 \(a_i\)。(从小到大也行。)
值得注意的一点就是:我们把 \(a_i\) 插入 \(x\),原本的 \(c_x\) 就不见了,也就是说两个数之间是否还会有数插入是有影响的,于是我们可以设计出一个和常规插入 DP 看起不太一样的状态(下文中连续段就是指中间不会再插入元素):
令 \(f(i, j)\) 表示填了 \(a_{1} \sim a_i\),有 \(j\) 个连续段的方案数。根据这个也可以确定出现在已经有 \(i - j\) 个 \(c\) 确定了,方便与 \(b\) 比较。
转移分三种类型:
-
\(a_i\) 自成一段,\(f(i, j) \leftarrow jf(i - 1, j - 1)\)。
-
\(a_i\) 插到某个连续段的开头/结尾,那么 \(c_{i - j} = a_i\),需满足 \(a_i < b_{i - j}\) 则有转移 \(f(i, j) \leftarrow 2jf(i - 1, j)\)
-
\(a_i\) 插入到两个连续段中间,把两个段合并起来,那么 \(c_{i - j} = c_{i - j - 1} = a_i\),需满足 \(a_i < b_{i - j}\) 则有转移 \(f(i, j) \leftarrow jf(i - 1, j + 1)\)。
初始状态:\(f(2, 2) = 1\)(插入 \(a_0, a_{n + 1}\)),目标状态:\(f(n + 2, 1)\),时间复杂度:\(O(n^2)\)。
顺嘴提一句从小到大的排序方式,是差不多的,状态一样,只是确定了 \(i + j\) 个 \(c\)(每段两端的也确定了)。初始状态是 \(f(0, 0) = 1\),目标状态是 \(f(n, 1)\)。本质是一样的,差别是一个段的两端的 \(c\) 是否确定了。
-
第一种转移,\(c_{i + j - 1} = c_{i + j} = a_i\),要求 \(a_i < b_{i + j - 1}\)
-
第二种,\(c_{i + j} = a_i\),要求 \(a_i < b_{i + j}\)
-
第三种没有 \(c\) 新确定。
总结:先确定好最优策略,提示我们根据 \(b, c\) 值的顺序进行插入,发现两个数之间是否还有数很重要,设计状态转移即可。
虽然我代吗写的是从小到大排序。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 5005, Mod = 998244353;
int n, a[MAXN], b[MAXN], dp[MAXN][MAXN];
int main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= n + 1; i++) {
cin >> b[i];
}
sort(a + 1, a + n + 1);
sort(b + 1, b + n + 2);
dp[0][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
dp[i][j] = 1ll * j * dp[i - 1][j + 1] % Mod; // 合并两段
if (i + j <= n + 1){
if (a[i] < b[i + j]) { // 加载一个段的开头、末尾
dp[i][j] = (dp[i][j] + 2ll * j * dp[i - 1][j]) % Mod;
}
if (a[i] < b[i + j - 1]) { // 自成一段
dp[i][j] = (dp[i][j] + 1ll * j * dp[i - 1][j - 1]) % Mod;
}
}
//cout << dp[i][j] << ' ';
}
//cout << '\n';
}
cout << dp[n][1];
return 0;
}
浙公网安备 33010602011771号