上一次写蓝桥杯总结还是关于广度优先搜索(bfs)的:https://blog.csdn.net/qq_41938259/article/details/104937474
现在,蓝桥杯也确定大致日期了,即今年的9、10月份。受到疫情影响,都呆家家里复习,的这一阶段主要是在学习和复习动态规划,这里会列几道基础的动态规划题目作为思路的讲解,主要选自于POJ和leetcode。以前一直不是很懂动态规划,这次经过老师的推荐,我看了中国大学MOOC上的一个讲解视频,确实恍然大悟,懂了不少,看完了有种拨开云雾见青天的感觉。这是视频的链接,是北京大学录制的:https://www.icourse163.org/learn/PKU-1001894005?tid=1450413466#/learn/announce
本文讲解动态规划主要分为四个部分:
我认为斐波那契数列就是最简单的动态规划之一,那什么是斐波那契数列呢?斐波那契数列是一个数列,这个数列从第3项开始,每一项都等于前两项之和。
那它和动态规划有什么关系呢?先别急我们先来解释下什么是动态规划思想。在我的认为里,动态规划是一种通过记录最终要得到的结果的中间“状态”,来慢慢递推结果的方式。在数学上,有点像高考那会做的数列递推式的题目。为什么说像呢?高考的时候往往这种数列题往往要你找到一个数列递推式,而动态规划也是如此,动态规划称这个递推式为“状态转移方程”。
这样说可能还是令人不是很懂,所以我要用斐波那契额数列举例。上文说过,斐波那契数列是一个数列,这个数列从第3项开始,每一项都等于前两项之和。那要求第n项的斐波那契数列,必然要求n-1项和n-2项,因为,所以一步步往前推可以推到和项,只有知道和项才可以指导第n项,所以我们可以用一个数组,记为dp来存放第i项的结果,其推导方式是(数组序号从0开始):
for(int i=2;i<=n;i++)
{
dp[i]=dp[i-1]+dp[i-2];
}
这就是所谓的“状态转移方程”。可以看出状态转移方程只是一个存放中间值的数组,你可以认为它是一种“散列”即序号是指某一时刻,而对应的值是这个时刻的状态。
什么是爬楼梯问题呢?就是设一共有n阶台阶,每次可以跨一步或者两步、三步,诸如此类云云。我们来看下leetcode的真题,这是链接:https://leetcode-cn.com/problems/three-steps-problem-lcci/
三步问题。有个小孩正在上楼梯,楼梯有n阶台阶,小孩一次可以上1阶、2阶或3阶。实现一种方法,计算小孩有多少种上楼梯的方式。结果可能很大,你需要对结果模1000000007。
示例1:
输入:n = 3
输出:4
说明: 有四种走法
示例2:输入:n = 5
输出:13
提示:n范围在[1, 1000000]之间
我们看到可以走三步,也可以两步、一步。那想想看当前这一步有几种走法应该怎么求呢?那不就是所有有可能的上一步走法的总数和吗。 于是我们可以开一个dp数组,它代表第i阶台阶有k种走法,即dp[i]=k。那递推式就是dp[i]=dp[i-1]+dp[i-2]+dp[i-3]了。所以我们需要知道前三阶台阶各有几种走法,不用多想第一阶是1种走法,第二阶梯是2种(两个1步或1个两步),第三阶实际上在题中给出了:4种。于是我们就可以通过dp[1]、dp[2]、dp[3]来求解第n阶台阶走法总数了。代码如下:
class Solution {
public:
int waysToStep(int n) {
long long int dp[1000001];
dp[1]=1;
dp[2]=2;
dp[3]=4;
for(int i=4;i<=n;i++)
{
dp[i]=(dp[i-1]+dp[i-2]+dp[i-3])%1000000007;
}
return dp[n];
}
};
还有一题类似的:https://leetcode-cn.com/problems/climbing-stairs/,这里直接给出参考代码,不在多说:
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
示例 2:输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
class Solution {
public:
int climbStairs(int n) {
int dp[10001];
dp[1]=1;
dp[2]=2;
for(int i=3;i<=n;i++)
{
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
};
基本一样,还更简单。下面来看数塔问题。
你在中学时期应该听说过“杨辉三角形”这个名字吧。一个数字等于它上一排两个相邻数字之和。不过数塔问题有点不同,他只是让你求从三角形的顶到三角形的底所有路径和中的最大的和。它所谓的“走法”类似杨辉三角形的那种相邻的规则。即用一个二维数组a来表示三角形,第a[i][j]只能往a[i+1][j]或者a[i+1][j+1]方向走。所以我们可以开一个二维的数组记为dp,dp[i][j]意思是到达第i行j列位置的最大路径和的值。dp[i][j]的求法是:dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+a[i][j]。即从底部开始递推,到i行j列最大值是到达与他相邻的两个元素最大值加上它自己的数值。最底层的数我们是知道的,从最底层各个元素到达最底层各个元素(即他们自己)的距离不就是各个元素它自己的大小吗?所以我们的先将最底层,记为n行,的dp数组赋值,再一层层向上推就可以了。dp数组最后一层的值即是a数组最后一层的值。
我们来看题目,这是来源于POJ百练专题的:http://bailian.openjudge.cn/practice/solution/24093078/
描述
7 3 8 8 1 0 2 7 4 4 4 5 2 6 5 (Figure 1)
Figure 1 shows a number triangle. Write a program that calculates the highest sum of numbers passed on a route that starts at the top and ends somewhere on the base. Each step can go either diagonally down to the left or diagonally down to the right.输入
Your program is to read from standard input. The first line contains one integer N: the number of rows in the triangle. The following N lines describe the data of the triangle. The number of rows in the triangle is > 1 but <= 100. The numbers in the triangle, all integers, are between 0 and 99.
输出
Your program is to write to standard output. The highest sum is written as an integer.
样例输入
5 7 3 8 8 1 0 2 7 4 4 4 5 2 6 5样例输出
30来源
IOI 1994
直接上代码,这里的a数组即是D数组;dp数组即是maxsum数组:
#include
#include
using namespace std;
#define MAX 101
int n;
int D[MAX][MAX];
int maxsum[MAX][MAX];
int Maxsum(int i,int j)
{
if (maxsum[i][j] != -1)
return maxsum[i][j];
if (i == n) maxsum[i][j]=D[i][j];
else
{
int x = Maxsum(i + 1, j + 1);
int y = Maxsum(i + 1, j);
maxsum[i][j] = max(x, y) + D[i][j];
}
return maxsum[i][j];
}
int main(void)
{
cin >> n;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= i; j++)
{
cin >> D[i][j];
maxsum[i][j] = -1;
}
cout << Maxsum(1, 1);
}
最长上升子序列就是在一个序列里按顺序找到递增的子序列,求这个子序列最长的长度。例如对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。这些子序列中最长的长度是4,是子序列(1, 3, 5, 8)。现在来看题,题目来自于POJ百练:http://bailian.openjudge.cn/practice/2757
描述
一个数的序列bi,当b1 < b2 < ... < bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1, a2, ..., aN),我们可以得到一些上升的子序列(ai1, ai2, ..., aiK),这里1 <= i1 < i2 < ... < iK <= N。比如,对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。这些子序列中最长的长度是4,比如子序列(1, 3, 5, 8).
你的任务,就是对于给定的序列,求出最长上升子序列的长度。输入
输入的第一行是序列的长度N (1 <= N <= 1000)。第二行给出序列中的N个整数,这些整数的取值范围都在0到10000。
输出
最长上升子序列的长度。
样例输入
7 1 7 3 5 9 4 8样例输出
4
我们来看,我们可以找“最长上升子序列”问题的子问题,假设要求的序列长为n。那就先求到n-1项的最长上升子序列问题,这样要是第n项数值大于第n-项数值的话,这说明加上最后一项人能构成一个新的最长上升子序列,所以它的长度是前者加一。反之长度不变,我们可以写出这样的代码:
if (a[i] > a[i-1])
dp[i] = max(dp[i], dp[i-1] + 1);
注意:dp数组的初始化问题,dp数组不初始化的话,运行到上面那里就会报空值的错误。所以怎么初始化呢?直接全赋值为1即可,为什么呢?在比较的时候还没计算dp[i]的话,dp[i]=1,那一定小于dp[i-1] + 1,因为dp[i-1]此时一定已经初始化了。要是dp[i]此时已经初始化了,那他就不是1,是其他的数,这是由于在先前的循环中求过dp[i],这回只是比较更新而已。现在来看代码:
#include
#include
using namespace std;
int a[1010], dp[1010];
int main(void)
{
int N;
cin >> N;
for (int i = 1; i <= N; i++)
{
cin >> a[i];
dp[i] = 1;
}
for(int i=2;i<=N;i++)
for (int j = 1; j < i; j++)
{
if (a[i] > a[j])
dp[i] = max(dp[i], dp[j] + 1);
}
cout << *max_element(dp + 1, dp + N + 1) << endl;
return 0;
}
什么是“最长公共子序列”呢?例如abcde和allllbkkkkkc它们的公共序列有a、ab、abc、bc其中最长的就是abc了,长度为3。我么来看这道POJ百练的题:http://bailian.openjudge.cn/practice/1458/
总时间限制:
1000ms
内存限制:
65536kB
描述
A subsequence of a given sequence is the given sequence with some elements (possible none) left out. Given a sequence X = < x1, x2, ..., xm > another sequence Z = < z1, z2, ..., zk > is a subsequence of X if there exists a strictly increasing sequence < i1, i2, ..., ik > of indices of X such that for all j = 1,2,...,k, xij = zj. For example, Z = < a, b, f, c > is a subsequence of X = < a, b, c, f, b, c > with index sequence < 1, 2, 4, 6 >. Given two sequences X and Y the problem is to find the length of the maximum-length common subsequence of X and Y.
输入
The program input is from the std input. Each data set in the input contains two strings representing the given sequences. The sequences are separated by any number of white spaces. The input data are correct.
输出
For each set of data the program prints on the standard output the length of the maximum-length common subsequence from the beginning of a separate line.
样例输入
abcfbc abfcab programming contest abcd mnp样例输出
4 2 0
具体思路是这样的:我们可以开一个二维的数组记为dp,dp[i][j]表示两个字符串str1到第i个字符的子串和str2到第j个字符的子串构成的最长子序列长度。递推式是这样的:
if(s1[i-1]==s2[j-1])
dp[i][j]=dp[i-1][j-1]+1;
当第str1的i-1个字符和str2的第j-1个字符一样时,长度就加一,依此类推。全部代码如下:
#include
#include
using namespace std;
char sz1[5005];
char sz2[5005];
int maxLen[5005][5005];
int main()
{
while( cin >> sz1 >> sz2 )
{
int length1 = strlen( sz1);
int length2 = strlen( sz2);
int nTmp;
int i,j;
for( i = 0;i <= length1; i ++ ) maxLen[i][0] = 0;
for( j = 0;j <= length2; j ++ ) maxLen[0][j] = 0;
for( i = 1;i <= length1;i ++ )
{
for( j = 1; j <= length2; j ++ )
{
if( sz1[i-1] == sz2[j-1] )
maxLen[i][j] = maxLen[i-1][j-1] + 1;
else
maxLen[i][j] = max(maxLen[i][j-1],maxLen[i-1][j]);
}
}
cout << maxLen[length1][length2] << endl;
}
return 0;
}
以后还有心得的话我会补充的。