神题!
地精部落
内存限制:128 MiB 时间限制:1000 ms 标准输入输出
题目描述
输入格式
输出格式
样例
数据范围与提示
题目大意
规定一个数如果比其左边右边数都小则称之为山谷,如果都大则称之为山峰。给定1-n(1-n不重复出现)的一个数列。求使山峰山谷交替出现的数列方案数
计数dp?
引理1
如果一个序列每个数ai置换为(n-ai)+1我们可以得到一个符合题意新数列,并且若原队列先降后增,则新数列先增后降
引理2
所有先降后增的方案数之和等于先增后降方案数之和
这两个引理非常重要
由因引理二可得:如果设f[i]为长度为i的山(固定j为山峰)并且前两个山分别为山峰和山谷(先降后增)时方案数,那么我们得出最后结果f[n]只需要*2就可得ans
你可能会疑问j是什么,别着急
固定j为山峰,那么j只有为奇数时才能转移。
fi可以由每一个固定j为山峰的位置转移。
首先因为先降后增那么f偶数项末尾一定为山谷,
然后我们从i-1个数中选择j-1个数,再让他们*f[j-1],即可将他们固定为先降后增。
然后又由于引理二得到后i-j个数中先增后降其实就是先降后增方案。
$f[i]=sum_{j为奇数}^{j<=i} f[j-1]*f[i-j]*$ $C_{i-1}^{j-1}$
转移就完了。
以下是本人(常数极大+丑陋+刻意压行)的代码
#include<bits/stdc++.h> using namespace std; #define ll long long #define A 3000000 ll C[4][A],f[A],n,p; int main(){ cin>>n>>p;f[0]=C[1][1]=C[0][0]=C[1][0]=1; for(ll i=1;i<=n;i++) for(ll j=1;j<=i;j++) { if(j&1) f[i]=(f[i]+f[j-1]%p*f[i-j]%p*C[(i-1)&1][j-1])%p; C[i&1][j]=(C[(i-1)&1][j]+C[(i-1)&1][j-1])%p; } cout<<(2*f[n])%p<<endl; }