当前位置: 首页 > news >正文

数据结构与算法学习笔记(Acwing提高课)----动态规划·最长上升子序列模型

数据结构与算法学习笔记(Acwing提高课)----动态规划·最长上升子序列模型

@@ author: 明月清了个风
@@ first publish time: 2025.4.26

ps⭐️基础课中已经学习过最长上升子序列模型的模版题,这是链接,可以先去把模版题复习一下,提高课中的题目是进一步的延伸和拓展,但是原理仍然是一样的。一共有八道题,还是比较多的,每道题之间的关联和递进也非常紧密。

AcWing 1017.怪盗基德的滑翔翼

怪盗基德是一个充满传奇色彩的怪盗,专门以珠宝为目标的超级盗窃犯。

而他最为突出的地方,就是他每次都能逃脱中村警部的重重围堵,而这也很大程度上是多亏了他随身携带的便于操作的滑翔翼。

有一天,怪盗基德像往常一样偷走了一颗珍贵的钻石,不料却被柯南小朋友识破了伪装,而他的滑翔翼的动力装置也被柯南踢出的足球破坏了。

不得已,怪盗基德只能操作受损的滑翔翼逃脱。

假设城市中一共有 N N N幢建筑排成一条线,每幢建筑的高度各不相同。

初始时,怪盗基德可以在任何一幢建筑的顶端。

他可以选择一个方向逃跑,但是不能中途改变方向(因为中森警部会在后面追击)。

因为滑翔翼动力装置受损,他只能往下滑行(即:只能从较高的建筑滑翔到较低的建筑)。

他希望尽可能多地经过不同建筑的顶部,这样可以减缓下降时的冲击力,减少受伤的可能性。

请问,他最多可以经过多少幢不同建筑的顶部(包含初始时的建筑)?

输入格式

输入数据第一行是一个整数 K K K,代表有 K K K组测试数据。

每组测试数据包含两行:第一行是一个整数 N N N,代表有 N N N幢建筑。第二行包含 N N N个不同的整数,每一个对应一幢建筑的高度 h h h,按照建筑的排列顺序给出。

输出格式

对于每一组测试数据,输出一行,包含一个整数,代表怪盗基德最多可以经过的建筑数量。

数据范围

1 ≤ K ≤ 100 1 \le K \le 100 1K100

1 ≤ N ≤ 100 1 \le N \le 100 1N100

0 ≤ h ≤ 10000 0 \le h \le 10000 0h10000

思路

提高课的题目都是基础课的进阶版本,还是比较建议先去看一下基础课的模版题再来看,因为最基础的模型不会在重新解释了,如果不知道可能会看不太懂。

对于最长上升子序列而言,需要求出的就是序列中连续上升的子序列的最长长度,这里进行简单回顾,对于一个序列而言,使用f[i]表示以第 i i i个数结尾的子序列的集合,要求的属性是最大值,因此可以根据第 i i i个数的上一个数,也就是子序列的倒数第二个数来进行集合的划分进而进行状态转移的计算。

在本题中,需要抽象出最长上升子序列的模型,因为可以从任意的楼顶出发,且只能向一个方向进行,因此肯定是选择一个子序列;并且要求子序列的是下降的,其实也就是以出发点结尾的最长上升子序列,因此可以使用最长上升子序列的方法解决。

注意点是可以选择方向,也就是可以向左的子序列也可以是向右的子序列,因此就是把最长上升子序列做两遍。

代码

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>using namespace std;const int N = 110;int n;
int f[N];
int a[N];int main()
{int T;cin >> T;while(T --){cin >> n;for(int i = 1; i <= n; i ++) cin >> a[i];int res = 0;for(int i = 1; i <= n; i ++){f[i] = 1;for(int j = 1; j < i; j ++)if(a[i] > a[j])f[i] = max(f[i], f[j] + 1);res = max(res, f[i]);}for(int i = n; i > 0; i --){f[i] = 1;for(int j = n; j > i; j --)if(a[i] > a[j])f[i] = max(f[i], f[j] + 1);res = max(res, f[i]);}cout << res << endl;}return 0;}

AcWing 1014. 登山

五一到了,ACM队组织大家去登山观光,队员们发现山上一共有 N N N个景点,并且决定按照顺序来浏览这些景点,即每次所浏览景点的编号都要大于前一个浏览景点的编号

同时队员们还有另一个登山习惯,就是不连续浏览海拔相同的两个景点,并且一旦开始下山,就不再向上走了

队员们希望在满足上面条件的同时,尽可能多的浏览景点,你能帮他们找出最多可能浏览的景点数么?

输入格式

第一行包含整数 N N N,表示景点数量。

第二行包含 N N N个整数,表示每个景点的海拔。

输出格式

输出一个整数,表示最多能浏览的景点数。

数据范围

2 ≤ N ≤ 1000 2 \le N \le 1000 2N1000

思路

对于这种应用题还是对模型进行抽象,首先是要按照顺序来浏览景点,也就是不能回头,意味着必须是子序列;并且连续的两个景点海拔不能相同,也就是必须要有变化;最后是开始下山就不能上山,也就是走过的路线一定是先上升再下降的。结合前面的就是我们需要找到一条先严格单调上升然后严格单调下降的最长路线。

由于路线是先上升再下降的,因此必有一个最高点,故可以根据该最高点进行分类,将所有景点存为 a i a_i ai,那么可以根据最高点分为最高点为 a 1 a_1 a1的路线,最高点为 a 2 a_2 a2的路线……,只要在这些值中求一个最大值即可。

那么如何求每一种的值呢,对于某一种路线——以 a k a_k ak为最高点的路线为例,该路线由左边的上升子序列和右边的下降子序列组合而成,而在最高点 a k a_k ak左右两边的景点之间没有关联,因此要求以 a k a_k ak为最高点的路线的最大值只要分别求左右两边的最大值即可。

到这里就可以发现其实求的和上一题是一样的,左右两边都算一次最长上升子序列。

需要注意的是因为两边的值都需要保留因此要开两个数组存储,在上一题中是直接覆盖的。

代码

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>using namespace std;const int N = 1100;int n;
int f[N];
int g[N];
int a[N];int main()
{cin >> n;for(int i = 1; i <= n; i ++) cin >> a[i];for(int i = 1; i <= n; i ++){f[i] = 1;for(int j = 1; j < i; j ++)if(a[i] > a[j])f[i] = max(f[i], f[j] + 1);}for(int i = n; i > 0; i --){g[i] = 1;for(int j = n; j > i; j --)if(a[i] > a[j])g[i] = max(g[i], g[j] + 1);}int res = 0;for(int i = 1; i <= n; i ++) res = max(res, f[i] + g[i] - 1);cout << res << endl;return 0;
}

AcWing 482. 合唱队形

N N N位同学站成一排,音乐老师要请其中的 ( N − K ) (N - K) NK位同学出列,使剩下的 K K K位同学排成合唱队形。

合唱队形是指这样的一种队形:设 K K K位同学从左到右依次便编号为 1 , 2 , ⋯ , K 1, 2, \cdots, K 12,K,他们的身高分别为 T 1 , T 2 , ⋯ , T K T_1, T_2, \cdots, T_K T1,T2,,TK,则他们的身高满足 T 1 < ⋯ < T i > T ( i + 1 ) ⋯ > T K ( 1 ≤ i ≤ K ) T_1 < \cdots < T_i > T_(i + 1) \cdots > T_K (1 \le i \le K) T1<<Ti>T(i+1)>TK(1iK)

你的任务是,已知所有 N N N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。

输入格式

第一行包含整数 N N N,表示同学的总数。

第二行有 N N N个整数,用空格分隔,第 i i i个整数 T i T_i Ti是第 i i i位同学的身高。

输出格式

输出包括一行,这一行只包含一个整数,就是最少需要几位同学出列。

数据范围

2 ≤ N ≤ 100 2 \le N \le 100 2N100

130 ≤ T i ≤ 230 130 \le T_i\le 230 130Ti230

思路

根据题意,所谓的合唱队形就是上一题中提到的先上升再下降的序列,只不过现在是要去掉最少得同学,也就是选择最多的同学形成这样的序列,因此完全一样,把上一题稍微改一下就行,这题问的是要删掉几个同学,因此输出改一下就行。

代码

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>using namespace std;const int N = 110;int n;
int f[N];
int g[N];
int a[N];int main()
{cin >> n;for(int i = 1; i <= n; i ++) cin >> a[i];for(int i = 1; i <= n; i ++){f[i] = 1;for(int j = 1; j < i; j ++)if(a[i] > a[j])f[i] = max(f[i], f[j] + 1);}for(int i = n; i > 0; i --){g[i] = 1;for(int j = n; j > i; j --)if(a[i] > a[j])g[i] = max(g[i], g[j] + 1);}int res = 0;for(int i = 1; i <= n; i ++) res = max(res, f[i] + g[i] - 1);cout << n - res << endl;return 0;
}

AcWing 1012. 友好城市

Palmia国有一条横贯东西的大河,河有笔直的南北两岸,岸上各有位置各不相同的 N N N个城市。

北岸的每个城市有且仅有一个友好城市在南岸,而且不同城市的友好城市不相同。

每对友好城市都向政府申请在河上开辟一条直线航道连接两个城市,但是由于河上雾太大,政府决定避免任意两条航道交叉,以避免事故。

编程帮助政府做出一些批准和拒绝申请的决定,使得在保证任意两条航线不相交的情况下,被批准的申请尽量多。

输入格式

第一行包含整数 N N N,表示城市数。

2 2 2行到第 n + 1 n + 1 n+1行,每行两个整数,中间用一个空格隔开,分别表示南岸和北岸的一对友好城市的坐标。

输出格式

仅一行,输出一个整数,表示政府所能批准的最多申请数。

数据范围

1 ≤ N ≤ 5000 1 \le N \le 5000 1N5000

0 ≤ x i ≤ 10000 0 \le x_i\le 10000 0xi10000

思路

根据题意,桥只能在友好城市之间建立且所有的桥不能相交,问题是能够建立最多的桥,给出的数据是所有友好城市对的坐标。

下图给出了一种城市的分布情况,上下之间由线相连的为友好城市对。其中A,B,C,D四座城市之间建立的就是非法桥梁,而图中红色桥梁表示的就是最大的配对情况。可以看出,当以某一条线的城市为基准找对面的城市时,对面的城市坐标一定是单调递增的,也就是上升子序列,当然作为基准的城市坐标也必须是单调上升的。要找到最大的配对数量,就是找到以一条线上的城市为基准后另一条线上的最长单调上升子序列,前提就是先将基准线的上城市坐标进行排序。

在这里插入图片描述

接下来就是代码的问题了,因为要排序的是一对坐标值,根据x或y进行排序就行,因此直接使用pair即可,这里的数据范围并不大,仍然可以用上面的方法解决,如果数据范围较大的话可以使用优化版本进行优化,优化版本在这个链接。

代码

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>using namespace std;typedef pair<int, int> PII;const int N = 5010;int n;
PII a[N];
int f[N];int main()
{cin >> n;for(int i = 0; i < n; i ++) cin >> a[i].first >> a[i].second;sort(a, a + n);int res = 0;for(int i = 0; i < n; i ++){f[i] = 1; for(int j = 0; j < i; j ++)if(a[i].second > a[j].second)f[i] = max(f[i], f[j] + 1);res = max(res, f[i]);}cout << res << endl;return 0;
}

AcWing 1016. 最大上升子序列和

一个数的序列 b i b_i bi,当 b 1 < b 2 < ⋯ < b S b_1 < b_2 < \cdots < b_S b1<b2<<bS时,我们称这个序列是上升的。

对于给定的一个序列( a 1 , a 2 , ⋯ , a N a_1, a_2, \cdots, a_N a1,a2,,aN),我们可以得到一些上升的子序列( a i 1 , a i 2 , ⋯ , a i k a_{i_1}, a_{i_2}, \cdots, a_{i_k} ai1,ai2,,aik),这里 1 ≤ i 1 < i 2 < ⋯ < i k ≤ N 1 \le i_1 < i_2 < \cdots < i_k \le N 1i1<i2<<ikN

比如对于序列 ( 1 , 7 , 3 , 5 , 9 , 4 , 8 ) (1, 7,3,5,9,4,8) (1,7,3,5,9,4,8),有它的一些上升子序列,如 ( 1 , 7 ) , ( 3 , 4 , 8 ) (1,7),(3,4,8) (1,7),(3,4,8)等等。

这些子序列中和最大的是 18 18 18,为子序列 ( 1 , 3 , 5 , 9 ) (1,3,5,9) (1,3,5,9)的和。

你的任务,就是对于给定的序列,求出最大上升子序列和。

注意,最长的上升子序列的和不一定是最大的,比如序列 ( 100 , 1 , 2 , 3 ) (100,1,2,3) (100,1,2,3)的最大上升子序列和为 100 100 100,而最长上升子序列为 ( 1 , 2 , 3 ) (1,2,3) (1,2,3)

输入格式

输入的第一行是序列的长度 N N N

第二行给出序列中的 N N N个整数,这些整数的取值范围是 0 ∼ 10000 0 \sim 10000 010000(可能重复)。

输出格式

输出一个整数,表示最大上升子序列和。

数据范围

1 ≤ N ≤ 1000 1 \le N \le 1000 1N1000

思路

这一题是最长上升子序列的变体,求的东西并不一样,同样采用动态规划的分析方式进行分析。

对于状态表示,还是使用f[i],表示所有以a[i]结尾的上升子序列,但属性值变成了这些子序列的和的最大值。

对于状态转移,同样根据倒数第二个数是哪个数进行划分。主要有两种情况,一种是没有其他数,那么f[i] = a[i];另一种是有倒数第二个数a[k],那么就可以根据f[k] + a[i]来计算最大值。

代码

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>using namespace std;const int N = 1010;int n;
int a[N];
int f[N];int main()
{cin >> n;for(int i = 1; i <= n; i ++) cin >> a[i];int res = 0;for(int i = 1; i <= n; i ++){f[i] = a[i];for(int j = 1; j < i; j ++)if(a[j] < a[i])f[i] = max(f[i], f[j] + a[i]);res = max(res, f[i]);}cout << res << endl;return 0;
}

AcWing 1010. 拦截导弹

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。

但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。

某天,雷达捕捉到敌国的导弹来袭。

由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

输入导弹依次飞来的高度(雷达给出的高度数据是不大于 30000 30000 30000的正整数,导弹数不超过 1000 1000 1000),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

输入格式

共一行,输入导弹依次飞来的高度。

输出格式

第一行包含一个整数,表示最多能拦截的导弹数。

第二行包含一个整数,表示要拦截所有导弹最少要配备的系统数。

数据范围

雷达给出的高度数据是不大于 30000 30000 30000的正整数,导弹数不超过 1000 1000 1000

思路

根据题意可知,导弹系统能够拦截的所有导弹的高度是依次下降的,因此第一问求的是一个最长下降子序列。

对于第二问而言,需要考虑如何进行导弹的分布,对于一套导弹系统而言,其所能拦截的导弹高度依次下降,对于一枚新的导弹,应更优先放在现有导弹系统中末尾导弹更小的系统中,这样浪费的区间会更小,也就是说,假设新的导弹高度为 50 50 50,现有的导弹系统中有一套末尾为 300 300 300,另一套末尾为 51 51 51,虽然两套系统都能拦截这个导弹,但很明显应该放在第二套里。因此考虑以下的贪心策略:

  1. 如果现有的子序列的结尾都小于当前数,则创建新子序列;
  2. 将当前数放在结尾大于等于它的最小的子序列后面。

证明该方法的正确性使用调整法即可,首先这肯定能找到一个合法方案,因此其答案肯定大于等于最优解,只要证明最优解大于等于我们的答案即可。假设对于导弹C,贪心法将其放在 A A A后面,而最优解将其放在了 B B B后面,如下所示,由于贪心策略保证了 A A A是大于 C C C的最小的书数,因此有 B ≥ A ≥ C B \ge A \ge C BAC,这也意味着最优解的 C C C以及 C C C后面的都能够放到 A A A后面,与最优解 A A A后面的进行交换,进而使最优解的这部分方案与贪心法的方案相同;进一步地,继续寻找下一个不同的数,重复如上操作,即可将最优解方案转化为贪心法方案,并且转化的过程中并不会增加导弹系统的个数,因此得证。

贪心法:**********AC
最优解:**********BC

我们可以使用一个数组g[]来存储每个导弹系统的最后一个数,每当有一个新的导弹来的时候,寻找这个数组中第一个大于等于它的最小的数,然后将这个数替换为新的数即可,如果没有大于等于它的数,那就新开一个导弹系统,通过这样可以发现,数组g[]是单调上升的,也就是一个单调上升子序列,这和Acwing 896. 最长上升子序列 II完全一致,因此可以发现一个序列最少可以用几个单调下降子序列覆盖和它的最长上升子序列的长度是一致的。

需要注意的是,这题的输入没有告诉我们有几个数,可以用stringstream读入。

代码

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>using namespace std;const int N = 1010;int n;
int f[N];
int g[N];
int a[N];int main()
{while(cin >> a[n]) n ++;int res = 0;for(int i = 0; i < n; i ++){f[i]= 1;for(int j = 0; j < i; j ++)if(a[j] >= a[i])f[i] = max(f[i], f[j] + 1);res = max(res, f[i]);}cout << res << endl;int cnt = 0;for(int i = 0; i < n; i ++){int k = 0;while(k < cnt && g[k] < a[i]) k ++;g[k] = a[i];if(k >= cnt) cnt ++;}cout << cnt << endl;return 0;
}   

AcWing 187. 导弹防御系统

为了对抗附近恶意国家的威胁, R R R国更新了他们的导弹防御系统

一套防御系统的导弹拦截高度要么一直 严格单调 上升要么一直 严格单调 下降。

例如,一套系统先后拦截了高度为 3 3 3和高度为 4 4 4的两发导弹,那么接下来该系统就只能拦截高度大于 4 4 4的导弹。

给定即将袭来的一系列导弹的高度,请你求出至少需要多少套防御系统,就可以将它们全部击落。

输入格式

输入包含多组测试用例。

对于每个测试用例,第一行包含整数 n n n,表示来袭导弹数量。

第二行包含 n n n个不同的整数,表示每个导弹的高度。

当输入测试用例 n = 0 n = 0 n=0时,表示输入终止,且该例无需处理。

输出格式

对于每个测试用例,输出一个占据一行的整数,表示所需的防御系统数量。

数据范围

1 ≤ n ≤ 50 1 \le n \le 50 1n50

思路

题目的背景和上一题一样,只是这题多了个另一种选择,防御系统的拦截高度可以是单调上升的,因此每一种导弹对应了两种情况,一种是放在上升序列中,另一种是放在下降序列中,在每种情况中,又分为了上一题中提到的两种,也就是放在现有的拦截系统中还是新开一个拦截系统。

由于没有更好的办法处理第一个选择,因此考虑使用暴搜完成,在上一题的框架下面套一层dfs即可。

一般来说求“最少”使用bfs,但是相对而言,bfs对于空间的要求较大,且对这一题而言比较难存数据,因此使用dfs,但dfs并不是求最小值的,因此需要通过记录一个全局变量即可求得最小值。第二份代码是迭代加深的方法求最小值。

代码-1(全局变量记录最小值)

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>using namespace std;const int N = 55;int up[N], down[N];
int n;
int q[N];
int ans;void dfs(int u, int su, int sd)
{if(su + sd >= ans) return;if(u == n){ans = su + sd;return;}int k = 0;while(k < su && up[k] < q[u]) k ++;int t = up[k];up[k] = q[u];if(k < su) dfs(u + 1, su , sd);else dfs(u + 1, su + 1, sd);up[k] = t;k = 0;while(k < sd && down[k] > q[u]) k ++;t = down[k];down[k] = q[u];if(k < sd) dfs(u + 1, su, sd);else dfs(u + 1, su, sd + 1);down[k] = t; 
}int main()
{while(cin >> n, n){for(int i = 0; i < n; i ++) cin >> q[i];ans = n;dfs(0, 0, 0);cout << ans << endl;}return 0;
}

代码-2(迭代加深求最小值)

#include <cstring>
#include <iostream>
#include <algorithm>using namespace std;const int N = 60;int n;
int h[N];
int up[N], down[N];bool dfs(int depth, int u, int su, int sd)
{// 如果上升序列个数 + 下降序列个数 > 总个数是上限,则回溯if (su + sd > depth) return false;if (u == n) return true;// 枚举放到上升子序列中的情况bool flag = false;for (int i = 1; i <= su; i ++ )if (up[i] < h[u]){int t = up[i];up[i] = h[u];if (dfs(depth, u + 1, su, sd)) return true;up[i] = t;flag = true;break;  // 注意由上述证明的贪心原理,只要找到第一个可以放的序列,就可以结束循环了}if (!flag)  // 如果不能放到任意一个序列后面,则单开一个新的序列{up[su + 1] = h[u];if (dfs(depth, u + 1, su + 1, sd)) return true;}// 枚举放到下降子序列中的情况flag = false;for (int i = 1; i <= sd; i ++ )if (down[i] > h[u]){int t = down[i];down[i] = h[u];if (dfs(depth, u + 1, su, sd)) return true;down[i] = t;flag = true;break;  // 注意由上述证明的贪心原理,只要找到第一个可以放的序列,就可以结束循环了}if (!flag)  // 如果不能放到任意一个序列后面,则单开一个新的序列{down[sd + 1] = h[u];if (dfs(depth, u + 1, su, sd + 1)) return true;}return false;
}int main()
{while (cin >> n, n){for (int i = 0; i < n; i ++ ) cin >> h[i];int depth = 0;while (!dfs(depth, 0, 0, 0)) depth ++ ;     // 迭代加深搜索cout << depth << endl;}return 0;
}

AcWing 272. 最长公共上升子序列

熊大妈的奶牛在小沐沐的熏陶下开始研究信息题目。

小沐沐先让奶牛研究了最长上升子序列,再让他们研究了最长公共子序列,现在又让他们研究最长公共上升子序列了。

小沐沐说,对于两个数列 A A A B B B,如果它们都包含一段位置不一定连续的数,且数值是严格递增的,那么称这一段数是两个数列的公共上升子序列,而所有的公共上升子序列中最长的就是最长公共上升子序列了。

奶牛半懂不懂,小沐沐要你来告诉奶牛什么是最长公共上升子序列。

不过,只要告诉奶牛它的长度就可以了。

数列 A A A B B B的长度均不超过 3000 3000 3000

输入格式

第一行包含一个整数 N N N,表示数列 A A A B B B的长度。

第二行包含 N N N个整数,表示数列 A A A

第二行包含 N N N个整数,表示数列 B B B

输出格式

输出一个整数,表示最长公共上升子序列的长度。

数据范围

1 ≤ N ≤ 3000 1 \le N \le 3000 1N3000,序列中的数字均不超过 2 31 − 1 2^{31} - 1 2311

思路

这就是最长公共子序列和最长上升子序列的结合版,建议先把这两个题复习一下再看这个,主要是状态划分上有变化

对于状态表示,使用f[i][j]表示所有由第一个序列的前 i i i个字母和第二个序列的前 j j j个字母构成的,且以b[j]结尾的公共上升子序列,要求的属性是最长的公共上升子序列。

对于状态划分,将f[i][j]划分为所有包含a[i]的公共上升子序列和所有不包含a[i]的公共上升子序列,对于后者而言,由于不包含a[i]所以它就是f[i - 1][j],但是对于前者而言,由于其包含a[i]且以b[j]结尾,因此要求a[i] == b[j],对于这一类可以根据倒数第二个数是哪个数进一步划分,也就是倒数第二个数是b[1], b[2], ......, b[j - 1]的情况,以b[k]举例,其表示倒数第二个数为b[k],也就是第一个序列的前 i i i个字母和第二个序列的前 k k k个字母组成的公共上升子序列,其表示为f[i, k],状态转移为f[i][j] = f[i, k] + 1。下面给出了朴素版本的代码,三重循环会超时。

代码

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>using namespace std;const int N = 3010;int n;
int a[N], b[N];
int f[N][N];int main()
{cin >> n;for(int i = 1; i <= n; i ++) cin >> a[i];for(int i = 1; i <= n; i ++) cin >> b[i];for(int i = 1; i <= n; i ++)for(int j = 1; j <= n; j ++){f[i][j] = f[i- 1][j];if(a[i] == b[j]){f[i][j] = max(f[i][j], 1);for(int k = 1; k < j; k ++){if(b[k] < b[j])f[i][j] = max(f[i][j], f[i][k] + 1);}}}int res = 0;for(int i = 1; i <= n; i ++) res = max(res, f[n][i]);cout << res << endl;return 0;
}

优化代码

// 这里只给出循环体里的优化了
// 由于循环体的第三重循环的前置条件为a[i] == b[j],因此循环体中的 if(b[k] < b[j])可以改为if(b[k] < a[i])
// 这样可以发现,f[i][j]在第三重循环中的更新条件是与变量j没有关系的,因此可以提到j的循环外面,且仅使用一个变量来记录f[i][k]的最大值,代码变换如下#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>using namespace std;const int N = 3010;int n;
int a[N], b[N];
int f[N][N];int main()
{cin >> n;for(int i = 1; i <= n; i ++) cin >> a[i];for(int i = 1; i <= n; i ++) cin >> b[i];for(int i = 1; i <= n; i ++){int maxv = 1;for(int j = 1; j <= n; j ++){f[i][j] = f[i - 1][j];if(a[i] == b[j]) f[i][j] = max(f[i][j], maxv);if(b[j] < a[i]) maxv = max(maxv, f[i][j] + 1);}}int res = 0;for(int i = 1; i <= n; i ++) res = max(res, f[n][i]);cout << res << endl;return 0;
}

相关文章:

  • 【Linux系统】Ext系列文件系统
  • 【黑马JavaWeb+AI知识梳理】前端Web基础01 - HTML+CSS
  • Java进阶--面向对象设计原则
  • 大规模数据同步后数据总条数对不上的系统性解决方案:从字段映射到全链路一致性保障
  • Sam算法基本原理解析
  • CPU与GPU的功能与区别解析
  • 运维面试情景题:如果有一块新的硬盘要加入机架如何配置;如果新加了一台服务器,如何配置安全措施
  • DeepSeek预训练追求极致的训练效率的做法
  • 2025.04.26-淘天春招笔试题-第三题
  • MQL5教程 06 EA开发实战
  • 【OSG学习笔记】Day 11: 文件格式与数据交换
  • Dify中的文本分词处理技术详解
  • 财务管理域——企业风控系统设计
  • Channel如何安全地尝试发送数据
  • win11右键菜单改回win10模式
  • 基于 RAG 的 Text2SQL 全过程的 Python 实现详解,结合 LangChain 框架实现自然语言到 SQL 的转换
  • 20250426在ubuntu20.04.2系统上解决问题mkfs.exfat command not found
  • function,bind,lambda的用法
  • 力扣刷题Day 31:删除链表的倒数第N个结点(19)
  • 数据库原理(1)
  • 持续更新丨伊朗内政部长:港口爆炸已致8人死亡750人受伤
  • 美联合健康集团高管枪杀案嫌疑人对谋杀指控不认罪
  • 为国出征指纹却无法识别?他刷新了我军在这一项目的最好成绩
  • 传媒湃︱《金陵晚报》副刊“雨花石”5月起改为免费刊登
  • 最高法:侵犯著作权罪中的“复制发行”不包括单纯发行行为
  • 巴基斯坦召开国家安全委员会紧急会议,应对印方连环举措