蓝桥杯模拟赛 摆动序列 详细题解 多种解法
大家好,我叫亓官劼(qí guān jié ),在CSDN中记录学习的点滴历程,时光荏苒,未来可期,加油~博客地址为:亓官劼的博客,亓官劼的博客2。
本文原创为亓官劼,请大家支持原创,部分平台一直在盗取博主的文章!!!
第十一届 蓝桥杯 省 模拟赛 完整题目+题解地址为:第十一届 蓝桥杯 省 模拟赛 试题+题解
有很多小伙伴反应说在题解中写的这题的动态规划的代码看不懂,不知道为什么要这样写,所以本文来详细的解释一下这题的解法,以及动态规划如何进行优化的过程。
题目
问题描述
如果一个序列的奇数项都比前一项大,偶数项都比前一项小,则称为一个摆动序列。即 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。
题解一:暴力+dfs
这题也可以使用暴力+dfs的方法进行一个暴力的搜索符合要求的序列,但是在本题的数量级中,此种解法必然超时,所以在此就不贴代码了,无意义。
题解二:动态规划(一)基本动态规划
这题既然暴力是无法过所有样例的话,那我们就需要寻求其他的解法了。这里先写一个动态规划的解法,我们来分析我们的目前的问题。问题为:如果一个序列的奇数项都比前一项大,偶数项都比前一项小,则称为一个摆动序列。即 a[2i]<a[2i-1], a[2i+1]>a[2i]。小明想知道,长度为 m,每个数都是 1 到 n 之间的正整数的摆动序列一共有多少个。
我们使用dp[i][j]
来表示在第i位数选择j时,可以选择的数量
。那么对于任意的dp[i][j]
,我们可以根据题意分为两种情况,即i
为奇数和i
为偶数。
当i为奇数时,根据题意,奇数项是要比前一项大,则我们的第i位数的选择数为j时,我们可以组成的序列的数量为:第i-1位时选择,数为1到j-1的和
,即dp[i][j] = dp[i-1][1] + dp[i-1][2] + ...... + dp[i-1][j-1]
。
当i为偶数时,根据题意,偶数项要比前一项小,则我们的第i位数为j时,我们可以组成的序列的数量为:第i-1位时,数为j+1到n的和
,即dp[i][j] = dp[i-1][j+1] + dp[i-1][j+2] + ....... + dp[i-1][n]
。
这样我们动态规划的状态转移方程就出来了:
- 当i为奇数时:
dp[i][j] = dp[i-1][1] + dp[i-1][2] + ...... + dp[i-1][j-1]
- 当i为偶数时:
dp[i][j] = dp[i-1][j+1] + dp[i-1][j+2] + ....... + dp[i-1][n]
下面我们来对dp数组进行初始化,我们发现我们每一项都需要使用到i-1位时的数据,即上一位的数据,同时我们发现当i = 1时,dp[1][j] = 1
是显然成立的。即我们第一位数选择j
时,只有一种序列。
这样我们整个的算法的分析就完成了,下面我们只需要将我们的想法使用程序来进行实现即可,完整的题解代码为:
#include <iostream>
using namespace std;
int dp[1004][1004];
int main() {
// m为长度,n为数的范围
int m,n;
cin>>m>>n;
for(int i = 1; i <= n; i++)
dp[1][i] = 1;
for(int i = 2; i <= m; i++){
for(int j = 1; j <= n; j++){
if(i&1){
int temp = 0;
for(int k = 1; k <= j-1; k++){
temp = (temp + dp[i-1][k]) % 10000;
}
dp[i][j] = temp;
} else{
int temp = 0;
for(int k = j+1; k <= n; k++){
temp = (temp + dp[i-1][k]) % 10000;
}
dp[i][j] = temp;
}
}
}
int ans = 0;
for(int i = 0; i <= n; i++){
ans += dp[m][i];
}
cout<<ans;
return 0;
}
需要注意的是,由于我们dp[i][j]
表示的是第i为选择数j时的序列的数量,所有我们最终m位数最大值为n时,我们可以组成的序列数量为dp[m][1] + dp[m][2] + ......... + dp[m][n]
。
此种解法的时间复杂度为O(mn2),即O(N3)的效率,对于本题的数据规模,我们发现只可以过80%的数据,因为题目要求对于所有评测用例,1 <= n, m <= 1000。所以我们本种动态规划解法只可以得到80%的分数,如果想要得到满分,我们还得进行进一步的优化。
题解三:动态规划(二) 进一步优化算法
我们在题解二中发现,即使我们使用动态规划算法,也只能有O(mn^2)的时间效率,在本题的数据规模中,只可以过前80%的数据,得到80%的分数,如果想要拿全分的话,那我们就只能进一步的优化我们的算法了。
我们分析题解二的算法,发现我们每次求解dp[i][j]
的时候都进行了一次重复的计算,就是计算第1到j-1项的和或者第j+1到n项的和,这里的计算我们是可以通过优化将它省略的。这样我们就可以将我们的时间复杂度从O(n3)降到O(n2),直接降低一个数量级。如果我们要省略这里的重复的求和计算,我们就需要改变我们这里dp[i][j]
存储的信息。这里我们改为使用dp[i][j]
表示的信息,当i为偶数时,dp[i][j]
表示第i个数选择小于等于j时的数列数;当i为奇数时,dp[i][j]
表示第i个数选择大于等于j是的数列数。
那么对于任意的dp[i][j]
,当i为奇数的时候,奇数项要比前一项大,所以我们当前的dp[i][j]
的值为dp[i-1][j-1]
+dp[i][j+1]
。即dp[i][j] = dp[i-1][j-1] + dp[i][j+1]
,由于我们此时的dp[i][j]
的值需要使用到dp[i][j+1]
,所以我们这里遍历倒着遍历。当i为偶数时,偶数项比前一项小,所以我们dp[i][j]
的值为dp[i-1][j+1]
+ dp[i][j-1]
。即dp[i][j] = dp[i-1][j+1] + dp[i][j-1]
,这里我们就需要进行正向的遍历。
在这种解法中,我们发现我们在dp[i][j]
更新的遍历中,代替了累加的计算。在i为奇数的时候,我们的j从n到1进行遍历,dp[i][j] = dp[i-1][j-1] + dp[i][j+1]
;当i为偶数时,我们的j从1到n进行遍历,dp[i][j] = dp[i-1][j+1] + dp[i][j-1]
。在dp[i][j]
的计算中直接记录了累加的值,避免了大量的计算。
这时我们的状态转移方程为:
-
当i为奇数时:
dp[i][j] = dp[i-1][j-1] + dp[i][j+1]
-
当j为偶数是:
dp[i][j] = dp[i-1][j-1] + dp[i][j-1]
这时我们就需要对dp数组进行初始化,在这种解法中,我们由于dp[i][j]
与解法二中表示的意义是不一样的,所以我们这的初始化也不同。在这里我们的dp[1][j] = n - i +1
。
这时我们的m如果为奇数时,所能组成的序列数量为dp[m][1]
,如果n为偶数,所能组成的序列数量为dp[m][n]
这样就将我们的算法的时间复杂度从O(N3)优化到了O(N2)。完整的题解代码为:
#include <iostream>
using namespace std;
int dp[1004][1004];
int main() {
// m为长度,n为数的范围
int m,n;
cin>>m>>n;
for(int i = 1; i <= n; i++)
dp[1][i] = n - i + 1;
for (int i = 0; i <= n; ++i) {
dp[0][i] = 1;
}
for(int i = 1; i <= m; i++)
if(i & 1)
for(int j = n; j >= 1; j--)
dp[i][j] = (dp[i-1][j-1] + dp[i][j+1]) % 10000;
else
for(int j = 1; j <= n; j++)
dp[i][j] = (dp[i-1][j+1] + dp[i][j-1]) % 10000;
int ans = m & 1 ? dp[m][1] : dp[m][n];
cout<<ans;
return 0;
}
大家好,我叫亓官劼(qí guān jié ),在CSDN中记录学习的点滴历程,时光荏苒,未来可期,加油~博客地址为:亓官劼的博客,亓官劼的博客2。
本文原创为亓官劼,请大家支持原创,部分平台一直在盗取博主的文章!!!