蓝桥杯 摆动序列
题目
问题描述
如果一个序列的奇数项都比前一项大,偶数项都比前一项小,则称为一个摆动序列。即 a[2i]<a[2i-1], a[2i+1]>a[2i]。
小明想知道,长度为 m,每个数都是 1 到 n 之间的正整数的摆动序列一共有多少个。
输入格式
输入一行包含两个整数 m,n。
输出格式
输出一个整数,表示答案。答案可能很大,请输出答案除以10000的余数。
样例输入
3 4
样例输出
14
样例说明
以下是符合要求的摆动序列:
2 1 2
2 1 3
2 1 4
3 1 2
3 1 3
3 1 4
3 2 3
3 2 4
4 1 2
4 1 3
4 1 4
4 2 3
4 2 4
4 3 4
评测用例规模与约定
对于 20% 的评测用例,1 <= n, m <= 5;
对于 50% 的评测用例,1 <= n, m <= 10;
对于 80% 的评测用例,1 <= n, m <= 100;
对于所有评测用例,1 <= n, m <= 1000。
题解
这题如果直接暴力求解的话,估计只能够过50%的数据,所以还是得使用dp来进行求解。dp[i][j]表示第i位数时,最大数为m时共有多少个。然后根据题目要求:如果一个序列的奇数项都比前一项大,偶数项都比前一项小,则称为一个摆动序列。所以我们每次处理都需要判断i的奇偶,这里使用i&1进行判断(有不懂的小伙伴可以百度一下),然后再进行相应的dp过程。
直接看代码,有详细讲解
第一行中,令 d[1][j]为:第1个数选择大于等于 j的数的方案总数。
从第二行开始:
奇数行中,令 d[i][j]为:第i个数选择大于等于j的数时的方案总数。
偶数行中,令 d[i][j]为:第i个数选择小于等于j的数时的方案总数。
即从第二行开始,如果行数为偶数行,那么我们当前可能的数目为:dp[i][j] = (dp[i-1][j+1] + dp[i][j-1]) % 10000;,如果为奇数行则:dp[i][j] = (dp[i-1][j-1] + dp[i][j+1]) % 10000;。
然后这样的话,如果我们总的长度为奇数的话,那么就是dp[m][1],如果是偶数,则为dp[m][n]。
#include<bits/stdc++.h>
using namespace std;
int dp[1004][1004];
int main(){
int m,n;
cin>>m>>n;
for(int i=1; i<=n; i++){//初始化为下一行i可以选择的值的数目
dp[1][i]=n-i+1; // 比如dp11 = 4,即是第一行可选4个
}
//注意 !!!!!!!!11
//第几行代表序列中填的第几个数
//在奇数行中,dp[i][j]含义为:第i个数选择大于等于j的数时的方案总数。
//在偶数行中,dp[i][j]含义为:第i个数选择小于等于j的数时的方案总数。
// 注意奇偶 奇偶 奇偶
for(int i=2;i<=m;i++){
if(i&1){
//奇数行,第i个数填大于等于j有多少种情况
//因为大于等于j的情况=等于j的情况 + 大于等于j+1的情况,
//而大于等于j+1的情况=等于j的情况 + 大于等于j+2的情况
//一直递推累加下去,这里我们从j取最大值开始,采用 倒序 动态记忆化叠加,思考/草稿一下为什么倒序,提示---记忆化
for(int j=n;j>=1;j--){
dp[i][j]= (dp[i-1][j-1]+dp[i][j+1])%10000; //不要忘了题目方案总数大于10000后要取余
//第一轮理解,这一i奇数行 选择大于等于j的方案数 (这时j=n 第一轮)
// = 上一行i-1偶数行中选择小于j(即小于等于j-1)的方案 与 最末尾最大的数j组合的方案数(即 dp[i-1][j-1]) + dp[i][j+1] (此时dp[i][j+1]=0 ,因为j=n,j+1不在1~n范围,所以为零)
//第二轮理解,这一i奇数行选择大于等于j的方案数 (这时j=n-- 第二轮)
// = 上一行i-1偶数行中选择小于等于j(即小于等于j-1)的方案 与j组合的方案数 (即 dp[i-1][j-1]) 加上 dp[i][j+1] (即 上一轮中这一奇数行j选择大于等于n的方案数)
//以此类推
}
}
else{//偶数行 ,第i个数填小于等于j有多少种情况
//因为小于等于j的情况=等于j的情况 + 小于等于j-1的情况,
//而小于等于j-1的情况=等于j的情况 + 小于等于j-2的情况
//一直递推累加下去,这里我们从j取最小值开始,采用 正序 动态记忆化叠加,思考/草稿一下为什么正序,提示---记忆化
for(int j=1;j<=n;j++){
dp[i][j]=dp[i-1][j+1]+dp[i][j-1]; //不要忘了题目方案总数大于10000后要取余
//第一轮理解,这一i偶数行 选择小于等于j的方案数 (这时j=1 第一轮)
// = 上一行i-1奇数行中选择大于j(即大于等于j+1)的方案 与 最开头最小的数j组合的方案数(即 dp[i-1][j+1]) + dp[i][j-1] (此时dp[i][j-1]=0 ,因为j=0,j-1不在1~n范围,所以为零)
//第二轮理解,这一i偶数行选择小于等于j的方案数 (这时j=1++ 第二轮)
// = 上一行i-1奇数行中选择大于j(即小于等于j+1)的方案 与 j组合的方案数 (即 dp[i-1][j+1]) 加上 dp[i][j-1] (即 上一轮中这一偶数行j选择小于等于1的方案数)
//以此类推
}
}
}
//最后,如果m长度为奇数,选择dp[m][1], 如果m长度为偶数,选择dp[m][n], 根据上面dp过程思考一下为什么
int ans = m & 1 ? dp[m][1] : dp[m][n];
//输出
cout<<ans;
return 0;
}