Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。
题目链接:洛谷 P2467
题目大意:求长度为 的波动数列的数量。
我们首先设立状态: 表示在 的全排列中,首项为山峰并且取值在 的方案数。
接下来考虑 如何进行转移来的。根据状态的设立,此状态首项的取值区间为 ,即 ,而此时 我们是已经算出来的了,那么接下来我们就只需要再考虑在 的全排列中,首项为山峰并且值为 的情况。
由于此时我们已经确定了首先的值,那么接下来我们就只需要再确定第 到第 项一共 项的值,不难发现,这 项的取值仍然是 的全排列,再由于我们整个序列不是向上()就是向下(),我们仍然容易得到:
相邻的两个位置不可能同时向上或者向下,也就是说如果此时 号位置是向上的,那么 号位置一定向下。
根据这样的结论,我们就可以确定第 号位置一定是向下的,那么在剩下来的 个数当中,哪些数全出来一定可以满足 号位置是向下的呢?这个就 比较显然了,如果要满足这个要求的话,那么 位置的数应在 当中选,用我们设立的状态来表示就是 了。
所以我们现在得到了状态转移方程:
但是到这里其实还没有完,注意我们在设立状态的时候提前说明了首项为山峰,那么首项为山谷的情况怎么办呢?其实同样考虑得到同样的方程,所以我们只需要把最后的结果翻倍就好了。
另外要注意的是,我们需要使用滚动数组优化第一维数组。
也许分析到这里,我们会突然觉得这道题不是很难,但是仔细回味一下这道题的过程,还是可以给我们很多的启示。
#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;
}