AcWing 1.2.1 最长上升子序列模型 + 动态规划 + 图解(详细)

(1)acwing 4557. 最长上升子序列 4557. 最长上升子序列 - AcWing题库

给定一个长度为 N 的整数序列 a1,a2,…,aN。请你计算该序列的最长上升子序列的长度。上升子序列是指数值严格单调递增的子序列

输入格式
  • 第一行包含整数 N
  • 第二行包含 N个整数 a1,a2,…,aN
输出格式
  • 一行,一个整数,表示最长上升子序列的长度
数据范围
  • 1≤N≤1000
  • 0≤ai≤100000
输入样例:
7
1 7 3 5 9 4 8
输出样例:
4

 C++代码:

AcWing 1.2.1 最长上升子序列模型 + 动态规划 + 图解(详细)_第1张图片

#include 
#include 
using namespace std;

const int N = 1010;

int n,res=0;
int arr[N],dp[N];

int main() {
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&arr[i]);
    for(int i=1;i<=n;i++) {
        dp[i]=1;
        for(int j=1;j

也可以这样写:

int main() {
    scanf("%d",&n);
    for(int i=0;i

(2)acwing1017-怪盗基德的滑翔翼

假设城市中一共有 N幢 建筑排成一条线,每幢建筑的高度各不相同。初始时,怪盗基德可以在任何一幢建筑的顶端。他可以选择一个方向逃跑,但是不能中途改变方向(因为中森警部会在后面追击)。因为滑翔翼动力装置受损,他只能往下滑行(即:只能从较高的建筑滑翔到较低的建筑)。他希望尽可能多地经过不同建筑的顶部,这样可以减缓下降时的冲击力,减少受伤的可能性。请问,最多可以经过多少幢不同建筑的顶部(包含初始时的建筑)

输入格式

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

输出格式

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

数据范围

  • 1≤K≤100,
  • 1≤N≤100,
  • 0

输入样例:

3
8
300 207 155 299 298 170 158 65
8
65 158 170 298 299 155 207 300
10
2 1 3 4 5 6 7 8 9 10

输出样例:

6
6
9

AcWing 1.2.1 最长上升子序列模型 + 动态规划 + 图解(详细)_第2张图片

思路:移动方向一旦确定,就转换成了LIS问题,那么可以看作是两个方向的最长上升子序列问题,答案res为正向逆向LIS两个过程中的较大者

AcWing 1.2.1 最长上升子序列模型 + 动态规划 + 图解(详细)_第3张图片 C++代码:

// 当确定完方向和起点后,最长的距离是什么呢?
// 起点:a[i]
// 最长距离:以a[i]为结尾的最长上升子序列

#include 
#include 

using namespace std;

const int N = 110;

int n;
int arr[N],dp[N];

int main() {
	int T;
	scanf_s("%d", &T);
	while (T--) {
		scanf_s("%d", &n);
		for (int i = 1; i <= n; i++) scanf_s("%d", &arr[i]);
		// 正向求解LIS问题
		int res = 0;
		for (int i = 1; i <= n; i++) {
			dp[i] = 1;
			for (int j = 1; j < i; j++) {
				if (arr[i] > arr[j]) dp[i] = max(dp[i], dp[j] + 1);
			}
			res = max(res, dp[i]);
		}
		// 反向求解LIS问题
		for (int i = n; i>=1; i--) {
			dp[i] = 1;
			for (int j = n; j > i; j--) {
				if (arr[i] > arr[j]) dp[i] = max(dp[i], dp[j] + 1);
			}
			res = max(res, dp[i]);
		}
		printf("%d\n", res);
	}
	return 0;
}

(3)acwing1014-登山问题

五一到了,ACM队 组织大家去登山观光,队员们发现山上一共有N个景点,并且决定按照顺序来浏览这些景点,即每次所浏览景点的编号都要大于前一个浏览景点的编号。同时队员们还有另一个登山习惯,就是不连续浏览海拔相同的两个景点,并且一旦开始下山,就不再向上走了。队员们希望在满足上面条件的同时,尽可能多的浏览景点,你能帮他们找出最多可能浏览的景点数么?

输入格式

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

输出格式

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

数据范围

  • 2≤N≤1000

输入样例

8
186 186 150 200 160 130 197 220

输出样例

4

AcWing 1.2.1 最长上升子序列模型 + 动态规划 + 图解(详细)_第4张图片

本题实质上是求正向反向两次LIS问题,两次的LIS过程相互独立,故所求为两端LIS过程中最长上升子序列的最大长度之和

  • res = max(res,f[i]+g[i]-1)

C++代码:

/*
	条件1:按照编号递增的顺序来浏览 => 必须是子序列
	条件2:相邻两个景点不能相同
	条件3:一旦开始下降,就不能上升了

	目标:求最多能浏览多少景点
*/

#include 
#include 

using namespace std;
const int N = 1010;

int n;
int arr[N];
int f[N], g[N];

int main() {
	scanf_s("%d", &n);
	for (int i = 1; i <= n; i++) scanf_s("%d", &arr[i]);
	for (int i = 1; i <= n; i++) {
		f[i] = 1;
		for (int j = 1; j < i; j++) {
			if (arr[i] > arr[j]) f[i] = max(f[i], f[j] + 1);
		}
	}
	for (int i = n; i >= 1; i--) {
		g[i] = 1;
		for (int j = n; j > i; j--) {
			if (arr[i] > arr[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);
	printf("%d\n", res);
	return 0;
}

(4)acwing 482.合唱队形 482. 合唱队形 - AcWing题库

N 位同学站成一排,音乐老师要请其中的 (N−K) 位同学出列,使得剩下的 K 位同学排成合唱队形。合唱队形是指这样的一种队形:设 K 位同学从左到右依次编号为 1,2…,K 他们的身高分别为 T1,T2,…,TK 则他们的身高满足 T1<…Ti+1>…>TK(1≤i≤K)。你的任务是,已知所有 N 位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。

输入格式
  • 输入的第一行是一个整数 N ,表示同学的总数
  • 第二行有 N 个整数,用空格分隔,第 i  个整数 Ti  是第 i 位同学的身高(厘米)
输出格式
  • 输出包括一行,这一行只包含一个整数,就是最少需要几位同学出列
数据范围
  • 2≤N≤100
  • 130≤T≤230

思路: 如果要使得出列的同学数量最少的话,就意味着要使剩下的数量最多。那意思就是要找到一个先上升再下降的序列,选个最大的。然后再用总数减去这个长度。

#include 
#include 

using namespace std;
const int N = 1010;

int n;
int arr[N];
int f[N],g[N];

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d", &arr[i]);
    for(int i=1;i<=n;i++) {
        f[i] = 1;
        for(int j=1;jarr[j]) f[i] = max(f[i], f[j] + 1);
        }
    }
    for(int i=n;i>=1;i--) {
        g[i] = 1;
        for(int j=n;j>i;j--) {
            if(arr[i]>arr[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);
    printf("%d",n-res);
    return 0;
}

(5)acwing 1012.友好城市

AcWing 1.2.1 最长上升子序列模型 + 动态规划 + 图解(详细)_第5张图片

输入样例: 

7
22 4
2 6
10 3
15 12
9 8
17 17
4 2

输出样例: 

4

AcWing 1.2.1 最长上升子序列模型 + 动态规划 + 图解(详细)_第6张图片

 AcWing 1.2.1 最长上升子序列模型 + 动态规划 + 图解(详细)_第7张图片

>>思路和分析

对于任意一种合法的建立桥梁的方式,都可以对应一种因变量的上升子序列。反之,对于因变量的任何一个上升子序列,我们都可以唯一的对应一个合法的建桥方式。每一种建桥方式,建桥的数量和上升子序列的长度是相同的。因此左边集合(所有合法的建桥方式)的长度最大值就等于右边集合(上升子序列)的长度的最大值。
解法:先按照自变量的大小,把因变量排序。排好之后得到一个序列,然后在序列当中求最长的一个上升子序列。
排序思路:看一下什么会出现相交的情况?
本质:如果选出来的桥梁是没有交叉的,那么就意味着按照其中某一个点来排序,那么另外一个点对应的位置也一定要使有序的才可以。因为一旦出现逆序,就是有交叉的。没有交叉也就意味着一定没有逆序的。那本题其实就可以转化为求最长上升子序列的长度。 

C++代码: 

#include 
#include 

using namespace std;

typedef pair PII;

const int N = 5010;

int n;
PII q[N];
int f[N];

int main() {
	scanf_s("%d", &n);
	for (int i = 0; i < n; i++) scanf_s("%d%d", &q[i].first, &q[i].second);
	sort(q, q + n);

	int res = 0;
	for (int i = 0; i < n; i++) {
		f[i] = 1;
		for (int j = 0; j < i; j++) {
			if (q[i].second > q[j].second) f[i] = max(f[i], f[j] + 1);
		}
		res = max(res, f[i]);
	}
	printf("%d\n",res);
	return 0;
}

(6)acwing 1016.最长上升子序列和

AcWing 1.2.1 最长上升子序列模型 + 动态规划 + 图解(详细)_第8张图片

输入样例:  

7
1 7 3 5 9 4 8

 输出样例: 

18

AcWing 1.2.1 最长上升子序列模型 + 动态规划 + 图解(详细)_第9张图片 C++代码:

#include 
#include 

using namespace std;

const int N = 1010;
int n;
int a[N], f[N];

int main() {
	scanf_s("%d", &n);
	for (int i = 1; i <= n; i++) scanf_s("%d", &a[i]);
	for (int i = 1; i <= n; i++) {
		f[i] = a[i];
		for (int j = 1; j  a[j]) f[i] = max(f[i], f[j] + a[i]);
		}
	}
	int res = 0;
	for (int i = 1; i <= n; i++) res = max(res, f[i]);
	printf("%d\n", res);
	return 0;
}

你可能感兴趣的:(最长上升子序列模型,动态规划,图解)