DP搬运工之搬运工2

longdie

2020-12-31 14:49:09

Personal

**注:以下系列题目为搬运 mengbier(yxy) 的DP经典题目。** # 题目 给你 $n(n<=50)$, $K(K<=n^2)$,问你有多少个1 到 $n$ 的排列, 满足相邻两个数的 $max$ 的和不超过 $K$, 输出答案 $mod20172019$ 的结果。 # 分析 这应该算是我做过的第一道**预设型DP**。 我们很显然的可以发现,如果从小到大考虑的话,会出现后效性,这就启发我们先预设后面的状态来进行DP和转移。 因为我们一般计算的是**当前情况**的贡献,我们可以通过**限制后面**情况来满足当前。 就本题而言:我们可以通过提前声明数 $x$ 两边可不可以继续填数来进行转移。 显然分三种情况: 1. 两边都不填,那么贡献会增加$2*x$。 2. 填一边,那么会增加$x$的贡献。 3. 都不填,那么不会增加贡献。 当然不要忘了讨论数放在端点上的情况。 我们可以定义 $f[i][j][k]$ 表示填完 $1 - i$, 有 $j$ 个位置可以填数,当前贡献为 $k$时的方案数。 分情况转移即可,复杂度$O(n^2K)$。 ## 代码 ```cpp #include <bits/stdc++.h> using namespace std; inline int read(int x = 0) { return scanf("%d", &x), x; } const int mod = 20172019, N = 55; int n, K, f[N][N][N*N], ans = 0; int main() { n = read(), K = read(); f[1][0][0] = 1; for(int i = 2; i <= n; ++i)//采用刷表法进行DP for(int j = 0; j <= i; ++j) for(int k = 0; k <= K; ++k) { int now = (f[i-1][j][k] * 2) % mod;//放在两端的情况。 f[i][j + 1][k] = (f[i][j + 1][k] + now) % mod; f[i][j][k + i] = (f[i][j][k + i] + now) % mod; now = f[i-1][j][k] * 1ll * j % mod;//放在中间的情况。 f[i][j + 1][k] = (f[i][j + 1][k] + now) % mod; f[i][j][k + i] = (f[i][j][k + i] + now * 2ll) % mod; f[i][j-1][k + 2*i] = (f[i][j-1][k + 2*i] + now) % mod; } for(int i = 0; i <= K; ++i) ans = (ans + f[n][0][i]) % mod;//统计答案。 return cout << ans, 0; } ```