洛谷 P2467 [SDOI2010]地精部落【动态规划】

103 阅读2分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

题目链接:洛谷 P2467

题目大意:求长度为 nn 的波动数列的数量。

我们首先设立状态:f[i][j]f[i][j] 表示在 ii 的全排列中,首项为山峰并且取值在 [1,j][1,j] 的方案数。

接下来考虑 f[i][j]f[i][j] 如何进行转移来的。根据状态的设立,此状态首项的取值区间为 [1,j][1,j] ,即 [1,j1]+[j,j][1,j-1]+[j,j],而此时 f[i][j1]f[i][j-1] 我们是已经算出来的了,那么接下来我们就只需要再考虑在 ii 的全排列中,首项为山峰并且值为 jj 的情况。

由于此时我们已经确定了首先的值,那么接下来我们就只需要再确定第 22 到第 ii 项一共 i1i-1 项的值,不难发现,这 i1i-1 项的取值仍然是 i1i-1 的全排列,再由于我们整个序列不是向上(a[i1]<a[i]>a[i+1]a[i-1]<a[i]>a[i+1])就是向下(a[i1]>a[i]<a[i+1]a[i-1]>a[i]<a[i+1]),我们仍然容易得到:

相邻的两个位置不可能同时向上或者向下,也就是说如果此时 ii 号位置是向上的,那么 i+1i+1 号位置一定向下。

根据这样的结论,我们就可以确定第 22 号位置一定是向下的,那么在剩下来的 [1,i1][1,i-1] 个数当中,哪些数全出来一定可以满足 22 号位置是向下的呢?这个就 比较显然了,如果要满足这个要求的话,那么 22 位置的数应在 [1,j1][1,j-1] 当中选,用我们设立的状态来表示就是 f[i1][(i1)(j1)]=f[i1][ij]f[i-1][(i-1)-(j-1)]=f[i-1][i-j] 了。

所以我们现在得到了状态转移方程:

f[i][j]=f[i][j1]+f[i1][ij]f[i][j]=f[i][j-1]+f[i-1][i-j]

但是到这里其实还没有完,注意我们在设立状态的时候提前说明了首项为山峰,那么首项为山谷的情况怎么办呢?其实同样考虑得到同样的方程,所以我们只需要把最后的结果翻倍就好了。

另外要注意的是,我们需要使用滚动数组优化第一维数组。

也许分析到这里,我们会突然觉得这道题不是很难,但是仔细回味一下这道题的过程,还是可以给我们很多的启示。

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;

const int N = 5e3 + 5;

int n, m, cur, f[2][N];

int main(){
    scanf("%d%d", &n, &m);
    if (n == 1) {
        puts("1");
        return 0;
    }
    f[0][1] = 1;
    for (int i = 2; i <= n; ++i) {
        cur ^= 1;
        for (int j = 1; j <= i; ++j) {
            f[cur][j] = (f[cur][j - 1] + f[cur ^ 1][i - j]) % m;
        }
    }
    printf("%d", f[cur][n] * 2 % m);
    return 0;
}