其他分享
首页 > 其他分享> > Re:从爆零开始的预设型DP考题——我们不生产DP,我们只是DP的搬运工(缓更)

Re:从爆零开始的预设型DP考题——我们不生产DP,我们只是DP的搬运工(缓更)

作者:互联网

题面

题面在这里

有的没的

预设型dp的意思大概是 枚举当前放哪个数。
搬运工系列都是计数dp。


First

dp当然要从设立dp数组开始。

定义状态 \(dp[i][j][k]\) 表示 填到位置 \(i\) 、还有 \(j\) 个位置可以填数、贡献总和为 \(k\) 的方案数。

假定从小到大填数,对于一个数 \(x\) ,分类讨论:

  1. 如果 \(x\) 的左右在之后的操作中都被填上了数,那它左右的数都比它大,所以当前的 \(x\) 不一定会产生贡献。

  2. 如果之后的操作仅在 \(x\) 的一边填上了数,那 \(x\) 仍比它另一边的数大,所以贡献是 \(x\) 。

  3. 如果之后的操作没有在 \(x\) 的任意一边填上数, \(x\) 仍比它两边都大,贡献为 \(2x\) 。

同理,来推新加入的数:

  1. 如果新填入的点在数列的两端,并且空出相邻的一个位置,能填数的位置 \(j + 1\)

Second


Third

首先先转化一下,可以固定排列 \(B\) ,因为一个排列 \(B\) 只会对应一个排列 \(A\) 。

排列 \(B\) 的方案数有 \(n!\) 种,所以只要最后对答案乘一个 \(n!\) 就行了。

不妨设排列 \(B\) 为 \(1\) 到 \(n\) 。

然后分类讨论:

  1. 位置 \(i\) 不填数:
    贡献不变,空出的位置数 \(j + 1\) 。
    从 \(i - 1\) 位置直接转移。

    \[dp[i][j + 1][k] += dp[i - 1][j][k] \]

  2. 把 \(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 \]

  3. 从之前没用的 \(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 \]

  4. 把 \(i\) 填到位置 \(i\) :
    这 \(j\) 肯定不变。
    则 \(A_{i} = B_{i}\),\(i\) 会产生贡献,贡献改变为 \(k + i\) 。
    方案仍直接转移。

    \[dp[i][j][k + i] += dp[i - 1][j][k] * j \]

  5. 既把 \(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