题目:
题解:
设计状态的话。。。
f[i][j]表示长度为i(可以理解为序列是1~i的排列),首项(强制是山峰)取值为[1,j]的方案数
转移方程就是:f[i][j]=f[i][j-1]+f[i-1][i-j]
step 1
首先来看f[i][j-1],直接累加首项为[1,j-1]的方案数
step 2
还有就是如何求首项为j的方案数呢
一般的,长度为i的状态都是由长度为i-1的状态转移来的
假设首项为j,依所设第一个必须是山峰,于是只需要算出首项取值为[1,j-1],长度为i-1,且第一个是山谷的序列的方案数即可, 记作g[i-1][j-1],【有点像把这些长度为i-1的序列粘在首项为j的后面(反正也大不过j,来了就当山谷呗)】
step 3
这个状态的另一个巧妙之处就是:我们强制第一个为山峰,其余的方案都可以通过取反得到
对于长度为i的数列,如果使数列的每一项ai变为i-ai+1,都可以得到恰好相反的一个数列,他们一一对应
【这个感觉就是把山谷变成了山峰,山峰变成山谷,方案数还是一样的啊】
于是
f[i][j]=g[i][i-j+1]
上面的g[i-1][j-1]=f[i-1][i-j],方程得解?
最后答案要*2(第一个还可以是山谷呢喂!)
要用滚动数组优化空间哦!
以下问题引自其他博主:
如果g[i-1][j-1]=f[i-1][i-j]含有数字j怎么办?
实际上只用在想象中将g[i-1][j-1]中大于等于j的数字全部加一,肯定能形成合法序列,并且也是一一对应的,因为ta们的相对大小并不发生变化,g的主要目的就是记录一个相对大小!
代码:
#include <cstdio>
#include <iostream>
using namespace std;
int n,mod,f[2][5000];
int main()
{
int i,j;
scanf("%d%d",&n,&mod);
if (n==1){printf("1");return 0;}
f[1][1]=1;
for (i=2;i<=n;i++)
for (j=1;j<=i;j++)
f[i&1][j]=(f[i&1][j-1]+f[(i-1)&1][i-j])%mod;
printf("%d",f[n&1][n]*2%mod);
}