动态规划练习题

目录

1.概念介绍
2.入门习题
3.习题练习
4.典型模版
5.经典框架练习
6,结语


1.介绍

返回目录

  1. 定义
    动态规划就是将一类多阶段问题的特点,把多阶段决策问题变成一系列互相联系的单阶段问题,然后逐个加以解决。动态规划就是解决这类问题的方法,动态规划算法通常用于求解具有某种最优性质的问题。

  2. 组成部分
    1.阶段
    把所给的问题的过程,恰当的分为若干个相互联系的阶段,以便能按照一定的次序去求解。描述阶段的变量成为阶段变量,常用K表示。阶段的划分,一般是根据时间和空间的自然特征来划分。

    2.状态
    状态表示每个阶段开始所处的自然状态或者客观条件,他描述了研究问题的过程的状态,又称为不可控因素。

    3.决策
    4.状态转化方程
    5.策略

  3. 过程
    动态规划最重要的就是构表和转化方程

    我们的第一步是先描述事物,一个好的描述等于成功了一半,描述的时候我们先不必管我们使用空间大小的问题。当我们可以准确的描述清楚一个物体的各个状态的时候,我们在开始考虑优化,看看有没有哪个描述是没有必要的,哪一个描述是可以通过另外一个值表示的

    我们可以通常一个表来记录所有已解的子问题的答案,不管该子问题以后是否被用到,只要他被计算过,就将其结果填入表中。在构表的时候,一个事物具有多个维度,我们在做题的时候是需要痛过多个维度才能表述清楚一个事物。当多维数组的各个维度的值确定的时候,那么就相当于确定了一个事物的一个状体,进而我们才能开始找状态转化方程。

下面我们将列举大量的经典的例题和相关练习,结合上文所说的理论。


2.入门练习

返回目录

开始下一题

  1. 生兔子
    有一对兔子,从出生后的第3个月起每个月都生一对兔子。小兔子长到第3个月后每个月又生一对兔子,假设所有的兔子都不死,问30个月内每个月的兔子总数为多少?
    从第1个月起开始计算:
    第一个月:1对
    第二个月:1对
    第三个月:2对(第一个月的一对生了一对小兔子)
    第四个月:3对(已有2对,第一个月的一对又生了一对小兔子,共3对)
    第五个月:5对(已有3对,第一个月的一对又生了一对小兔子,加上第三个月出生的那对兔子又生了一对,共5对)
    利用斐波那契数列很容易得到解决。

返回上一题
返回目录

  1. 错排信封
    小a同时给n个网友每人写了一封信,他把所有的信都装错了信封!(意思是第i个信不放在第i个信封里)
    现在的问题是:请大家帮小a同学计算一下,一共有多少种可能的错误方式呢?

    分析:
    设n封信,所有信都装错的情侣是fn
    当添加到n封信的时候,我们可以直接想到,这封信与前n-1封信中的一封放错的进行交换,那么我们就有n-1种选择。但还有一种情况就是前n-1封信中有一封没有放错,这封没有放错的信封和第n封信交换也是可以保证都放错。
    所以我们可以得到著名的错排公式
    dp[1] = 0. dp[2] = 1;
    dp[i] = (i - 1) * (dp[i - 1] + dp[i - 2]). (i >= 3)
    代码如下

    #include
    using namespace std;
    const int N = 100;
    int f[N]; // i个人放错信封的可能个数
    int main(){int n;cin >> n;f[1] = 0;f[2] = 1;for(int i = 2; i <= n; i++) {f[i] = (i - 1) * (f[i - 1] + f[i - 2]);}cout<

3.例题练习

返回目录

开始下一题

  1. k好数
    问题描述
    如果一个自然数N的K进制表示中任意的相邻的两位都不是相邻的数字,那么我们就说这个数是K好数。求L位K进制数中K好数的数目。例如K = 4,L = 2的时候,所有K好数为11、13、20、22、30、31、33 共7个。由于这个数目很大,请你输出它对1000000007取模后的值。
    输入格式
    输入包含两个正整数,K和L。
    输出格式
    输出一个整数,表示答案对1000000007取模后的值。

    样例输入
    4 2
    样例输出
    7
    数据规模与约定
    对于30%的数据,KL <= 106;
    对于50%的数据,K <= 16, L <= 10;
    对于100%的数据,1 <= K,L <= 100。
    分析:
    题目要求相邻两位不是相邻的数字,所以我们在建立表的时候就需要知道当前一位的前一位的值,同时我们还要记录我们到了第几位,所以我们建立一个dp[i][j]的表,i表示第i位,j表示第i位数。

    那么转换方程就不难写了
    dp[i][j] += dp[1 - 1][z] 当 |z - j| != 1

    但有一个需要注意的点既然是要L位k进制,比如k = 4,l = 2,例如12是可以的,但是02却是不行的。所以在我们输出答案的时候,需要在i ==l时候 去掉j = 0的情况。
    还有就是注意每一步都要取模 ,(同余定理)

    //#include
    //#include
    //#include
    //using namespace std;
    //const int N = 101;
    //const int mod = 1e9 + 7;
    //int cp[N][N] = {0};
    //int k,l,ans = 0;
    //int main(){
    //    cin>>k>>l;
    //    memset(cp,0,sizeof(cp));
    //    for(int i = 0; i < k; i++) cp[1][i] = 1; //初始化
    //
    //    for(int i = 2; i <= l ; i++){ //长度为i
    //        for(int j = 0; j < k; j++){ //末位数为j
    //            for(int z = 0; z < k; z++){ //统计和
    //                if(abs(z - j) != 1) cp[i][j] = (cp[i][j] + cp[i-1][z]) % mod;
    //            }
    //        }
    //    }
    //    for(int i = 1; i < k; i++) ans = (ans + cp[l][i]) % mod;
    //    cout<

返回上一题
开始下一题

  1. 最大的算式
    问题描述
      题目很简单,给出N个数字,不改变它们的相对位置,在中间加入K个乘号和N-K-1个加号,(括号随便加)使最终结果尽量大。因为乘号和加号一共就是N-1个了,所以恰好每两个相邻数字之间都有一个符号。例如:
    N=5,K=2,5个数字分别为1、2、3、4、5,可以加成:
      12(3+4+5)=24
      1*(2+3)(4+5)=45
      (1
    2+3)*(4+5)=45
      ……
    输入格式
    输入文件共有二行,第一行为两个有空格隔开的整数,表示N和K,其中(2<=N<=15, 0<=K<=N-1)。第二行为 N个用空格隔开的数字(每个数字在0到9之间)。
    输出格式
    输出文件仅一行包含一个整数,表示要求的最大的结果

    样例输入
    5 2
    1 2 3 4 5
    样例输出
    120
    样例说明
    (1+2+3)45=120

    分析:
    我们有dp[i][j] 来表述这个事件,i表示前用i个乘号,j表示前j个数

    我们还是先考虑当前的的情况
    假设我们已经用了a个乘号,到啦第b个数。
    我们思考动态规划解决当前问题要使用之前的答案,所以我们在已经用了a-1个乘号的基础上进行枚举第a个乘号的位置。
    转换方程如下:
    dp[i][j] = max(dp[i - 1][z] * (sum[j] - sum[z]))
    (我们使用一个sum记录前缀和来增加效率。)

    还有一点很重要的就是初始化dp[0][i] = sum[i] (0 <= i <= n) 意义就是乘号为0的时候,当然每一项就都是其前缀和。

    代码如下(下面的dp将i和j的位置调换了,但意义一样)

    //#include
    //using namespace std;
    //const int N = 16;
    //long long dp[N][N]; //i前面有j个乘号的最大值
    //int num[N];
    //int sum[N];
    //int main(){
    //    int n,k,t;
    //    cin>>n>>k;
    //    for(int i = 1; i <= n; i++) {
    //        cin>>num[i];
    //        sum[i] = sum[i - 1] + num[i]; //统计前n项和
    //        dp[i][0] = sum[i]; //初始化
    //    }
    //    for(int i = 2; i <= n; i++){ // 前i个
    //        t = min(i - 1,k);
    //        for(int j = 1; j <= t; j++){ //乘号
    //            for(int k = 1; k < i; k++){
    //                dp[i][j] = max(dp[i][j],dp[k][j - 1] * (sum[i] - sum[k]));
    //            }
    //        }
    //    }
    //    cout<

返回上一题
开始下一题

  1. 弹簧板
    有一个小球掉落在一串连续的弹簧板上,小球落到某一个弹簧板后,会被弹到某一个地点,直到小球被弹到弹簧板以外的地方。
    假设有 n 个连续的弹簧板,每个弹簧板占一个单位距离,a[i]代表代表第 i个弹簧板会把小球向前弹 a[i]个距离。比如位置 1 的弹簧能让小球前进 2个距离到达位置 3。如果小球落到某个弹簧板后,经过一系列弹跳会被弹出弹簧板,那么小球就能从这个弹簧板弹出来。现在希望你计算出小球从任意一个弹簧板落下,最多会被弹多少次后,才会弹出弹簧板。

    输入格式
    第一个行输入一个 nn 代表一共有 nn 个弹簧板。第二行输入 nn 个数字,中间用空格分开。第 ii 个数字 a[i]a[i] 代表第 ii 个弹簧板可以让小球移动的距离。

    数据约定:
    对于 50% 的数据:1 \le n \le 1000, 0 < a[i] \leq 301≤n≤1000,0 对于 100% 的数据:1 \le n \le 100000, 0 < a[i] \leq 301≤n≤100000,0

    输出格式
    输出一个整数,代表小球最多经过多少次才能弹出弹簧板。

    样例输入
    5
    2 2 3 1 2
    样例输出
    3

    分析:
    这一题我们从后往前推,(顺便补充一句,对于动态规划的递推过程可以从前往后,也可以从后往前。)
    dp[i]表示处于第i个弹簧板跳出所需要的次数
    因为我们是从后往前,所以我们如何利用后面的答案呢?通过i+a[i]就可以后面的答案。所以我们就不难得到转换方程
    转换方程
    dp[i] = dp[i + a[i]] + 1;
    代码如下

    #include
    using namespace std;
    const int N = 100;
    int dp[N]; //表示在第i个弹簧板弹出所需要的次数
    int a[N]; //表示i个弹簧板的前进距离
    int main(){int n,ans = -1;cin >> n;for(int i = 1; i <= n; i++){cin>>a[i];}for(int i = n; i >= 1; i--){dp[i] = dp[i + a[i]] + 1;ans = max(dp[i], ans); //同时取最大值}cout<

返回上一题
开始下一题

  1. 新游戏
    工作空闲之余,小ai经常带着同事们做游戏,最近小ai发明了一个好玩的新游戏:n 位同事围成一个圈,同事 A 手里拿着一个兔妮妮的娃娃。小ai喊游戏开始,每位手里拿着娃娃的同事可以选择将娃娃传给左边或者右边的同学,当小ai喊游戏结束时,停止传娃娃。此时手里拿着娃娃的同事即是败者。

    玩了几轮之后,小ai想到一个问题:有多少种不同的方法,使得从同事 A 开始传娃娃,传了 m 次之后又回到了同事 A 手里。两种方法,如果接娃娃的同事不同,或者接娃娃的顺序不同均视为不同的方法。例如1−>2−>3−>1 和 1->3->2->1 是两种不同的方法。
    输入格式
    输入一行,输入两个整数n,m(3≤n≤30,1≤m≤30),表示一共有 n 位同事一起游戏,一共传 m 次娃娃。
    输出格式
    输出一行,输出一个整数,表示一共有多少种不同的传娃娃方法。
    样例输入
    3 3
    样例输出
    2
    分析:
    题目限制了传递次数,同时还需要回到a手里。所以我们需要记录传递了多少次,现在在谁手上。
    因此,我们建立一个表dp[i][j],i表示传递了i次,j表示现在球在编号为j的手上的方法个数。
    当前的球可能来自前面一个人,也可以来自后面一个人。
    同时这是一个环,所以我们需要分类讨论。
    当j == 1 dp[i][j] = dp[i - 1][2] + dp[i - 1][n];
    当j == n dp[i][j] = dp[i - 1][1] + dp[i - 1][n - 1];
    其他的情况 dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j + 1];
    代码如下

    #include
    using namespace std;
    const int  N = 100;
    int dp[N][N];
    int main(){int n,m;cin >> n >> m;dp[0][1] = 1; // 刚开始传0次在1手上的种类是1for(int i = 1; i <= m; i++) {for(int j = 1; j <= n; j++) {if(j == 1) {dp[i][j] = dp[i - 1][2] + dp[i - 1][n];} else if(j == n) {dp[i][j] = dp[i - 1][1] + dp[i - 1][n -1];} else {dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j + 1];}}}cout<

返回上一题
开始下一题

  1. 逃生
    小ai在玩一款逃生的游戏。在一个 n×m 的矩形地图上,小ai位于其中一个点。地图上每个格子有加血的药剂,和掉血的火焰,药剂的药效不同,火焰的大小也不同,每个格子上有一个数字,如果格子上的数字是正数说明是一个药剂代表增加的生命值,如果是负数说明是火焰代表失去的生命值。
    小ai初始化有 v 点血量,他的血量上限是 c,任何时刻他的生命值都不能大于血量上限,如果血量为 0 则会死亡,不能继续游戏。
    矩形地图上的四个角(1,1),(1,m),(n,1),(n,m)为游戏的出口。游戏中只要选定了一个出口,就必须朝着这个方向走。例如,选择了左下的出口,就只能往左和下两个方向前进,选择了右上的出口,就只能往右和上两个方向前进,左上和右下方向的出口同理。
    如果成功逃生,那么剩余生命值越高,则游戏分数越高。为了能拿到最高分,请你帮忙计算如果成功逃生最多能剩余多少血量,如果不能逃生输出 −1-1−1。

    输入格式
    第一行依次输入整数 n,m,x,y,v,c(1 接下来 nnn 行,每行有 m 个数字,代表地图信息。(每个数字的绝对值不大于100,地图中小ai的初始位置的值一定为 0)

    输出格式
    一行输出一个数字,代表成功逃生最多剩余的血量,如果失败输出 −1。

    样例输入
    4 4 3 2 5 10
    1 2 3 4
    -1 -2 -3 -4
    4 0 2 1
    -4 -3 -2 -1

    样例输出
    10
    分析:
    还是考虑当前位置,如果我们在小ai的初始位置的左上方,那么前面一步只可能是下面和右边迈来的,所以dp[i][j] = dp[i -1][j] + dp[i][j -1].
    而对于四个方向,我们只需要在最外层套上一层循环即可。
    方向数组为
    tx = {1,-1,1,-1}
    ty = {1,1,-1,-1} //

    代码如下

    #include
    using namespace std;
    const int N = 100;
    const int INF = 0x7fffffff;
    int dp[N][N];
    int a[N][N];
    int xx[4] = {-1,-1,1,1};
    int yy[4] = {-1,1,-1,1};
    int main() {int n,m,x,y,v,c;cin >> n >> m >> x >> y >> v >> c;for (int i = 1; i <= n; i++) {for (int j = 1; j <= m; j++) {cin >> a[i][j];}}for (int t = 0; t < 4; t++) {for (int i = x; i > 0 && i <= n; i -= xx[t]) {for (int j = y; j > 0 && j <= m ; j -= yy[t]) {if (i == x && j == y) {dp[i][j] = v;} else if (i == x) {dp[i][j] = min(c, dp[i][j + yy[t]] + a[i][j]);} else if (j == y) {dp[i][j] = min(c, dp[i + xx[t]][j] + a[i][j]);} else {dp[i][j] = min(c, max(dp[i + xx[t]][j], dp[i][j + yy[t]]));}if (dp[i][j] <= 0) dp[i][j] = -INF; // 如果dp <= 0 则表示小ai失败了}}}int ans = max(dp[1][1], max(dp[1][n], max(dp[n][1], dp[n][n])));if (ans <= 0) {cout << "-1" <

返回上一题
开始下一题

  1. 一维消消乐
    游戏是这样的,你可以选择若干个相临的珠子,让他们同时消去。每一对珠子的消失都会使得总分数加上两颗珠子相乘的分数。注意,每个珠子只能消一次,并且珠子消去以后,还会占位。

    输入格式
    输入第一行一个整数n(1 <= n <= 10000)
    接下来一行输入n个整数wi(-1000 <= wi <= 1000)

    输出格式
    输出最大的分数

    样例输入
    8
    -9 -5 -4 -2 4 -5 -4 2

    样例输出
    73

    分析:
    我们还是考虑当前情况。
    第n个位置,我现在的选择是消去还是不消去,
    如果我选择消去,那么我需要前面一个珠子不消去,然后把前面一个珠子不消去的分数加上两者分数的乘积。
    如果我选择不消去,那么我的值就等于前一个珠子消去或者不消去的最大值。
    所以我们建立一个dp[n][2] , o表示不消去,1表示消去。

    所以转换方程为
    dp[i][0] = max(dp[i - 1][0], dp[i -1][1])
    dp[i][1] = dp[i - 1][0] + a[i] * a[i - 1]
    代码如下

    #include
    using namespace std;
    const int N = 100;
    int dp[N][2];
    int w[N];
    int main() {int n;cin >> n;for (int i = 1; i <= n; i++) {cin >> w[i];}dp[1][1] = 0;dp[1][0] = 0;for (int i = 2; i <= n; i++){dp[i][0] = max(dp[i -1][0], dp[i - 1][1]);dp[i][1] = dp[i - 1][0] + w[i] * w[i -1];}cout << max(dp[n][0], dp[n][1]) << endl;return 0;
    }
    

返回上一题
开始下一题

  1. 涂色问题
    小ai觉得白色的墙面好单调,他决定给房间的墙面涂上颜色。他买了 3 种颜料分别是红、黄、蓝,然后把房间的墙壁竖直地划分成 n 个部分,小ai希望每个相邻的部分颜色不能相同。他想知道一共有多少种给房间上色的方案。
    例如,当 n = 5时,下面就是一种合法方案。
    蓝 红 黄 红 黄

    由于墙壁是一个环形,所以下面这个方案就是不合法的。
    蓝 红 黄 红 蓝

    输入格式
    一个整数 n,表示房间被划分成多少部分。(1≤n≤50)

    输出格式
    一个整数,表示给墙壁涂色的合法方案数。

    样例输入
    4
    样例输出
    18

    分析:
    我们考虑当前位置n
    因为相邻不可以同色,我们想可不可以把第n个插入已经排好的合理解?

    经过思考,一共有两种可能。
    当n-1和1同色时,第n位置就有2种选择。因为n-1和1同色,我们就选择n-2的合理解,dp[n - 2] * 2
    当n-1和1不同色时,第n位置就只有一种选择。 dp[n - 1]
    所以我们可以构建出转换方程 将之前的两者进行相加。
    dp[n] = dp[n -1] + dp[n -2] * 2

    注意:这一题的dp值在n>30的情况下是非常大的,转换公式大于同级的斐波那契,所以需要用longlong类型。

    代码如下

    #include
    using namespace std;
    const int N = 100;
    typedef long long ll;
    ll dp[N];
    int main() {int n;cin >> n;dp[1] = 0;dp[2] = 6;for (int i = 1; i <= n; i++){dp[i] = dp[i - 1] + dp[i - 2] * 2;}cout << dp[n] << endl;
    }

返回上一题
返回目录

  1. 过河
    在一个夜黑风高的晚上,有 n 个小朋友在桥的这边,现在他们需要过桥,但是由于桥很窄,每次只允许不超过两人通过,他们只有一个手电筒,所以每次过桥后,需要有人把手电筒带回来,第 i 号小朋友过桥的时间为 a[i],两个人过桥的总时间为二者中时间长者。问所有小朋友过桥的总时间最短是多少。

    输入格式

    第一行输入一个整数 n (1 <=n <= 1000) ,表示有 n 个小朋友。

    第二行有 n 个整数 ai,ai​ 表示第 i 个小朋友过河需要的时间(0

    输出格式

    输出一个整数,表示所有小朋友过河所需要的时间
    样例输入
    4
    1 2 5 10

    样例输出
    17

    分析:
    我们考虑到你要过河,此时手电筒在对面
    因为题目说一次可以过两个人,我们思考有哪些可能?
    第一种让过河时间最少的人过来送手电筒,然后带着你一起过河。
    第二种让过河时间最少的人过来送手电筒,然后你带着n-1号一起过河,此时手电筒在对岸,接着让过河时间第二少的人过来送手电筒,带着过河时间最少的人一起过河。
    所以我们建立一个dp[i]来记录前i个人过河的最短时间。
    那么转换方程为
    dp[i] = max(dp[i - 1] + a[0] + a[i], dp[i -2] + a[0] + a[i] + 2 * a[1]);

    代码如下 :

    #include
    using namespace std;
    const int N = 100;
    int dp[N];
    int tim[N];
    int main() {int n;cin >> n;for (int i = 0; i <= n - 1; i++) {cin >> tim[i];}sort(tim, tim + n); //对过时间进行排序dp[0] = tim[0];dp[1] = tim[1];for (int i = 2; i < n; i++){dp[i] = max((dp[i - 1] + tim[0] + tim[i]), (dp[i - 2] + tim[0] + tim[i] + 2 * tim[1]));}cout << dp[n -1] << endl;return 0;
    }

4.典型框架

返回目录

1最长上升子序列(LIS)
2最长公共子序列(LCS)
3最长公共上升子序列(LCIS)
4最大子段和
5编译距离


1最长上升子序列

返回列表

Description
一个数的序列bi,当b1< b2 < … < bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1,a2,…,aN),我们可以得到一些上升的子序列(ai1, ai2, …, aiK),这里1<=i1  你的任务,就是对于给定的序列,求出最长上升子序列的长度。
Input
输入的第一行是序列的长度N(1<=N<=1000)。第二行给出序列中的N个整数,这些整数的取值范围都在0到10000。
Output
最长上升子序列的长度。
Sample Input
6
1 6 2 5 4 7
Sample Output
4

#include
using namespace std;
const int N = 100;
int dp[N];
int w[N];
int main(){int n,ans = -1;cin >> n;for (int i = 1; i <= n; i++) {cin >> w[i];}dp[1] = 1;for (int i = 2; i <= n; i++) {dp[i] = 1;for (int j = 1; j <= i; j++){if (w[i] > w[j]) {dp[i] = max((dp[j] + 1), dp[i]);}}ans = max(ans, dp[i]);}cout << ans << endl;return 0;
}

2最长公共子序列

返回列表

LCS是Longest Common Subsequence的缩写,即最长公共子序列。一个序列,如果是两个或多个已知序列的子序列,且是所有子序列中最长的,则为最长公共子序列。
比如,对于char x[]=“aabcd”;有顺序且相互相邻的aabc是其子序列,有顺序但是不相邻的abc也是其公共子序列。即,只要得出序列中各个元素属于所给出的数列,就是子序列。
再加上char y[]=“12abcabcd”;对比出才可以得出最长公共子序列abcd。

#include
#include
using namespace std;
const int N = 100;
int dp[N][N];int main() {string s1;string s2;cin >> s1 >> s2;int len1 = int(s1.size());int len2 = int(s2.size());memset(dp, 0, sizeof(dp));for (int i = 1; i <= len1; i++) {for (int j = 1; j <= len2; j++){if (s1[i - 1] == s2[j - 1]) {dp[i][j] = dp[i - 1][j - 1] + 1;} else {dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);}}}cout << dp[len1][len2] <

3最长公共上升子序列

返回列表

例如 1 3 4 3 6 和 1 4 2 6 的最长公共上升子序列为 1 4 6 答案为3。
代码如下

#include
using namespace std;
const int N = 1001;
int a[N],b[N];
int dp[N][N];int main() {int n, m, maxx;cin >> n >> m;for (int i = 1; i <= n; i++) {cin >> a[i];}for (int j = 1; j <= m; j++) {cin >> b[j];}for(int i = 1; i <= n; i++) {maxx = 0;for(int j = 1; j <= m; j++) {dp[i][j] = dp[i - 1][j];if(a[i] > b[j] && maxx < dp[i - 1][j]) {maxx = dp[i - 1][j];    //维护一个前ij的最大dp值} else if (a[i] == b[j]) {dp[i][j] = maxx + 1;}}}maxx = 0;for (int i = 1; i <= m; i++) {if (maxx < dp[n][i]) maxx = dp[n][i];}cout << maxx;
}

4最大子段和

放回列表

给出一段序列,选出其中连续且非空的一段使得这段和最大。

#include
using namespace std;const int MAXN=200005;int dp[MAXN],a[MAXN],n,maxx=-100000; int main(){scanf("%d",&n);for(int i=1;i<=n;i++){scanf("%d",&a[i]);}for(int i=1;i<=n;i++){dp[i]=max(a[i],dp[i-1]+a[i]);maxx=max(maxx,dp[i]);}printf("%d\n",maxx);return 0;
}

5编译距离

返回列表
博文链接
https://blog.csdn.net/caipengbenren/article/details/87637780


5.经典框架练习
下面的练习题都是在经典框架的基础上,进行的改变。

返回目录


//跳木桩
说有一列树桩:高低不一;如:9 5 8 3 9 4 1
只能跳比等于或小于当前木桩高度;不能回头跳;
问你选一个木桩来跳能跳到最多的木桩,并输出可以踩的最多的木桩个数
方法 : 从后往前使用一次最长上升子序列,或者从前往后做一次最长下降子序列


//删除最少的元素
给定有 n 个数的 A 序列:A1,A2,A3…An 。对于这个序列,我们想得到一个子序列 Ap1,Ap2⋯Api⋯Apm(1≤p1< p2<⋯pi<⋯< pm≤n),满足 Ap1≥Ap2≥⋯≥Api≤⋯≤Apm 。从 A 序列最少删除多少元素,可以得到我们想要的子序列。
输入格式
第一行输入一个整数 n,代表 A 序列中数字的个数。第二个输入 n 个整数,代表A1,A2 ,A3 …An。(1≤n≤1000,1≤Ai≤10000)
输出格式
输出需要删除的元素个数,占一行。
样例输入
7
3 2 4 1 2 5 3
样例输出
2

方法从头做一遍最长不上升子序列 从尾做一遍不上升子序列 再枚举中间的点 寻找最大值


//闯关
有一个游戏,里面包括了若干个关卡,可以从头往后挑一些关卡打,每个关卡有不同的难度,当挑战来一个关卡后,只能选择后面的关卡继续游戏,你爱挑战难度,你希望每次挑战的关卡难度是递增的,并且挑战的所以关卡难度和最大,他想知道这个最大和是多少。

输入格式
第一行输入一个整数n表示总关卡数

接下来的一行输入n个整数,a1,a2,a3,…an,代表矩阵这一行的n个数(1 <= n <= 10^3,1<= ai <= 10^9)
输出格式
输出一个整数,代表你挑战的关卡的难度和的最大值
样例输入
3
1 3 2
样例输出
4

方法:使用最长上升子序列的基础上 进行改变


//回文串
一个字符串如果从左往右读和从右往左读都一样,那么这个字符串是一个回文串。例如:”abcba”,”abccba”。
蒜头君想通过添加字符把一个非回文字符串变成回文串。例如:”trit”,可以添加一个’i’ 变成回文串”tirit”。请你用程序计算出,对于一个给定的字符串,最少需要添加几个字符,才能变成回文串。
输入格式
输入一个长度为n(1≤n≤3000) 的字符串。(字符串只包含字母)
输出格式
输出最少需要添加的字符个数,占一行。
样例输入
trit
样例输出
1

方法:将一个字符串 正着和反着做一次最长公共子序列


//小爱的日志
小ai喜欢把做过的事情记录下来,写在日志里,为了安全起见,它还有一份备份放在另外的地方,不过很不幸的是,最近他的两份日志都受到了破坏,有增加删除修改,但没有改变原来的顺序,两份受到的破坏不一定一样,小ai记录事情都是按照时间顺序的,记录的也都是时间戳,所以正确的记录上时间戳肯定是严格递增的,并且只有两份记录上都出现了时间戳他才认为有可能自己做了,现在他想知道他最多做了多少事情。

输入格式
第一行输入两个整数n,m代表两份记录的长度(1 <= n,m <= 10^3)
接下来一行输入n个整数a1,a2,a3…an,代表第一份记录的n个时间戳
接下来的一行输入m个整数,b1,b2,b3…bm.代表第二份记录的m个时间戳

输出格式
输出一个整数,代表小ai最多可能做了多少事情

样例输入
3 2
1 3 2
1 2
样例输出
2

方法:最长公共上升子序列


6.结语

返回目录

上面的题都是来自各个oj平台,如需对自己的进行测评,可以百度原题链接。


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章