Re:从爆零开始的预设型DP考题——我们不生产DP,我们只是DP的搬运工(缓更)
作者:互联网
题面
题面在这里
有的没的
预设型dp的意思大概是 枚举当前放哪个数。
搬运工系列都是计数dp。
First
dp当然要从设立dp数组开始。
定义状态 \(dp[i][j][k]\) 表示 填到位置 \(i\) 、还有 \(j\) 个位置可以填数、贡献总和为 \(k\) 的方案数。
假定从小到大填数,对于一个数 \(x\) ,分类讨论:
-
如果 \(x\) 的左右在之后的操作中都被填上了数,那它左右的数都比它大,所以当前的 \(x\) 不一定会产生贡献。
-
如果之后的操作仅在 \(x\) 的一边填上了数,那 \(x\) 仍比它另一边的数大,所以贡献是 \(x\) 。
-
如果之后的操作没有在 \(x\) 的任意一边填上数, \(x\) 仍比它两边都大,贡献为 \(2x\) 。
同理,来推新加入的数:
- 如果新填入的点在数列的两端,并且空出相邻的一个位置,能填数的位置 \(j + 1\)
Second
Third
首先先转化一下,可以固定排列 \(B\) ,因为一个排列 \(B\) 只会对应一个排列 \(A\) 。
排列 \(B\) 的方案数有 \(n!\) 种,所以只要最后对答案乘一个 \(n!\) 就行了。
不妨设排列 \(B\) 为 \(1\) 到 \(n\) 。
然后分类讨论:
- 位置 \(i\) 不填数:
贡献不变,空出的位置数 \(j + 1\) 。
从 \(i - 1\) 位置直接转移。\[dp[i][j + 1][k] += dp[i - 1][j][k] \] - 把 \(i\) 插入到之前的 \(j\) 个位置中的一个:
插入了该位置,空的位置数 \(j --\) ,但位置 \(i\) 也空出来了, \(j++\) ,所以, \(j\) 不变。
因为 \(B_{j} = j\) ,并且 \(i > j\) ,所以 \(i\)会产生贡献,贡献改变为 \(k + i\) 。
再者一共有 \(j\) 个选点,可乘法原理得。\[dp[i][j][k + i] += dp[i - 1][j][k] * j \] - 从之前没用的 \(j\) 中选一个插到位置 \(i\) :
\(i\) 向前跳了 \(1\) ,\(j++\) 。但位置 \(i\) 又被填上了, \(j\) 不变。
同理 \(B_{i} = i\) ,\(A_{i} = j\) , \(i > j\) ,所以 \(i\) 会产生贡献,贡献改变为 \(k + i\) 。
同样,有 \(j\) 个数待选,乘法原理得。\[dp[i][j][k + i] += dp[i - 1][j][k] * j \] - 把 \(i\) 填到位置 \(i\) :
这 \(j\) 肯定不变。
则 \(A_{i} = B_{i}\),\(i\) 会产生贡献,贡献改变为 \(k + i\) 。
方案仍直接转移。\[dp[i][j][k + i] += dp[i - 1][j][k] * j \] - 既把 \(i\) 插入到之前的 \(j\) 个位置,也从 \(j\) 中选一个数填到位置 \(i\) :
结合上边说的情况2,3,可知 \(j\) 仍然不变。
两个 \(i\) 都产生贡献,贡献改变为 \(k + i + i\) 。
同样,乘法原理。\[dp[i][j - 1][k + i + i] += dp[i - 1][j][k] * j * j \]
当然,情况2到4可以合并。
\[dp[i][j][k + i] += dp[i - 1][j][k] * (2 * j + 1) \]最后的答案就是
\[\sum_{i = k}^{n * n} dp[n][0][i] \]Code
#include<cstdio>
#include<algorithm>
#define LL long long
using namespace std;
const int MAXN = 55;
const int Mod = 998244353;
int n, m;
LL dp[MAXN][MAXN][MAXN * MAXN];
//dp[i][j][k] 表示填到了数字 i, i 之前有 j 个位置没有填, 当前产生的价值为 k
LL ans;
int main(){
scanf("%d%d", &n, &m);
dp[0][0][0] = 1;
dp[1][0][1] = 1;
dp[1][1][0] = 1;
for(register int i = 2; i <= n; i++){
for(register int j = 0; j <= i - 1; j++){ //到i最多有i - 1个位置没有填数
for(register int k = 0; k <= i * i; k++){ //贡献最大的话是i * i
if(!dp[i - 1][j][k]) continue; //如果为0的话肯定是没法达成这种情况,没法向后续转移
dp[i][j + 1][k] = (dp[i][j + 1][k] + dp[i - 1][j][k]) % Mod;
dp[i][j][k + i] = (dp[i][j][k + i] + dp[i - 1][j][k] * j) % Mod;
dp[i][j][k + i] = (dp[i][j][k + i] + dp[i - 1][j][k] * j) % Mod;
dp[i][j][k + i] = (dp[i][j][k + i] + dp[i - 1][j][k]) % Mod;
if(!j) continue;
dp[i][j - 1][k + i + i] = (dp[i][j - 1][k + i + i] + dp[i - 1][j][k] * j * j) % Mod;
}
}
}
for(register int i = m; i <= n * n; i++)
ans = (ans + dp[n][0][i]) % Mod;
for(register int i = 2; i <= n; i++)
ans = 1LL * ans * i % Mod;
printf("%lld", ans);
return 0;
}
标签:爆零,int,位置,贡献,Re,填数,MAXN,DP,dp 来源: https://www.cnblogs.com/TSTYFST/p/16515258.html