动态规划练习题
目录
1.概念介绍
2.入门习题
3.习题练习
4.典型模版
5.经典框架练习
6,结语
1.介绍
返回目录
-
定义
动态规划就是将一类多阶段问题的特点,把多阶段决策问题变成一系列互相联系的单阶段问题,然后逐个加以解决。动态规划就是解决这类问题的方法,动态规划算法通常用于求解具有某种最优性质的问题。 -
组成部分
1.阶段
把所给的问题的过程,恰当的分为若干个相互联系的阶段,以便能按照一定的次序去求解。描述阶段的变量成为阶段变量,常用K表示。阶段的划分,一般是根据时间和空间的自然特征来划分。2.状态
状态表示每个阶段开始所处的自然状态或者客观条件,他描述了研究问题的过程的状态,又称为不可控因素。3.决策
4.状态转化方程
5.策略 -
过程
动态规划最重要的就是构表和转化方程我们的第一步是先描述事物,一个好的描述等于成功了一半,描述的时候我们先不必管我们使用空间大小的问题。当我们可以准确的描述清楚一个物体的各个状态的时候,我们在开始考虑优化,看看有没有哪个描述是没有必要的,哪一个描述是可以通过另外一个值表示的
我们可以通常一个表来记录所有已解的子问题的答案,不管该子问题以后是否被用到,只要他被计算过,就将其结果填入表中。在构表的时候,一个事物具有多个维度,我们在做题的时候是需要痛过多个维度才能表述清楚一个事物。当多维数组的各个维度的值确定的时候,那么就相当于确定了一个事物的一个状体,进而我们才能开始找状态转化方程。
下面我们将列举大量的经典的例题和相关练习,结合上文所说的理论。
2.入门练习
返回目录
开始下一题
- 生兔子
有一对兔子,从出生后的第3个月起每个月都生一对兔子。小兔子长到第3个月后每个月又生一对兔子,假设所有的兔子都不死,问30个月内每个月的兔子总数为多少?
从第1个月起开始计算:
第一个月:1对
第二个月:1对
第三个月:2对(第一个月的一对生了一对小兔子)
第四个月:3对(已有2对,第一个月的一对又生了一对小兔子,共3对)
第五个月:5对(已有3对,第一个月的一对又生了一对小兔子,加上第三个月出生的那对兔子又生了一对,共5对)
利用斐波那契数列很容易得到解决。
返回上一题
返回目录
-
错排信封
小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)
代码如下#includeusing 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.例题练习
返回目录
开始下一题
-
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<
返回上一题
开始下一题
-
最大的算式
问题描述
题目很简单,给出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
(12+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<
返回上一题
开始下一题
-
弹簧板
有一个小球掉落在一串连续的弹簧板上,小球落到某一个弹簧板后,会被弹到某一个地点,直到小球被弹到弹簧板以外的地方。
假设有 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;
代码如下#includeusing 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<
返回上一题
开始下一题
-
新游戏
工作空闲之余,小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];
代码如下#includeusing 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<
返回上一题
开始下一题
-
逃生
小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} //代码如下
#includeusing 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" <
返回上一题
开始下一题
-
一维消消乐
游戏是这样的,你可以选择若干个相临的珠子,让他们同时消去。每一对珠子的消失都会使得总分数加上两颗珠子相乘的分数。注意,每个珠子只能消一次,并且珠子消去以后,还会占位。输入格式
输入第一行一个整数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]
代码如下#includeusing 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; }
返回上一题
开始下一题
-
涂色问题
小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类型。
代码如下
#includeusing 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; }
返回上一题
返回目录
-
过河
在一个夜黑风高的晚上,有 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]);代码如下 :
#includeusing 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平台,如需对自己的进行测评,可以百度原题链接。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
