嗯没错…这道题我是没有做起的…硬生生刚了两小时还停留在找规律的阶段…
在网上看了题解发现很多都写得并不那么清楚,反正我没有完全弄懂(可能是因为我比较垃圾)。于是我在观摩了网上大神的题解后再加上自己的一点题后思考,写下这一篇自认为大众都能看懂的题解(会打脸的吧)。
这道题毕竟还是一道省选题,本身没有代码的实现难度也不存在很多难的知识,所以只是对选手的思维难度要求非常高,比如状态的设立:
表示在 的全排列中,首项为山峰并且取值在 的方案数。
没错这个状态出来就十分神奇了,毕竟我是第一次见到这种状态的设立。
考虑 是如何转移来的。由于根据状态的设立,此状态首项的取值区间为 ,即 ,而此时 我们是已经算出来的了,那么接下来我们就只需要再考虑在 的全排列中,首项为山峰并且值为 的情况。
由于此时我们已经确定了首先的值,那么接下来我们就只需要再确定第 到第 项一共 项的值,不难发现,这 项的取值仍然是 的全排列,再由于我们整个序列不是向上( )就是向下( ),我们仍然容易得到:相邻的两个位置不可能同时向上或者向下,也就是说如果此时 号位置是向上的,那么 号位置一定向下。
根据这样的结论,我们就可以确定第
号位置一定是向下的,那么在剩下来的
个数当中,哪些数全出来一定可以满足
号位置是向下的呢?这个就
比较显然了,如果要满足这个要求的话,那么
位置的数应在
当中选,用我们设立的状态来表示就是
了。
所以我们现在得到了状态转移方程:
但是到这里其实还没有完,注意我们在设立状态的时候提前说明了首项为山峰,那么首项为山谷的情况怎么办呢?其实同样考虑得到同样的方程,所以我们只需要把最后的结果翻倍就好了。
另,滚动数组优化第一维。
也许分析到这里,我们会突然觉得这道题不是很难,但是仔细回味一下这道题的过程,还是可以给我们很多的启示。
参考代码:
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int Max=5e3+5;
int N,M,Cur,DP[2][Max];
int main(){
int I,J,K;
scanf("%d%d",&N,&M);
if(N==1){
puts("1");return 0;
}DP[0][1]=1;
for(I=2;I<=N;I++){
Cur^=1;
for(J=1;J<=I;J++){
DP[Cur][J]=(DP[Cur][J-1]+DP[Cur^1][I-J])%M;
}
}
printf("%d",DP[Cur][N]*2%M);
return 0;
}