【C++】CSP-J复赛dp问题(含模板题)

由于我之前写过一篇关于dp算法的文章了,所以,这里就不多阐释关于dp的内容了,详情请见这篇文章:【C++竞赛】note:动态规划(DP)


接下来让我们看几道题,来让我们更好感受一下dp的使用。

(版权说明:以下所有题目均来自于洛谷

例题一、模版题:B3637 最长上升子序列

题目如下:

B3637 最长上升子序列

题目描述

这是一个简单的动规板子题。

给出一个由 n ( n ≤ 5000 ) n(n\le 5000) n(n≤5000) 个不超过 10 6 10^6 106 的正整数组成的序列。请输出这个序列的最长上升子序列的长度。

最长上升子序列是指,从原序列中按顺序尽可能多 取出一些数字排在一起,这些数字是逐渐增大的。

输入格式

第一行,一个整数 n n n,表示序列长度。

第二行有 n n n 个整数,表示这个序列。

输出格式

一个整数表示答案。

输入输出样例 #1
输入 #1
复制代码
6
1 2 4 1 3 4
输出 #1
复制代码
4
说明/提示

分别取出 1 1 1、 2 2 2、 3 3 3、 4 4 4 即可。

题目分析

如果一个上升子序列的最大值小于一个在它后面的数,那么这个数和这个子序列可以拼成一个更长的上升子序列。

意思是:给你一串数字,要你按顺序挑出尽量多的数字,而且挑出来的数字必须一个比一个大(严格递增)。

接下来,我们分析一下样例一:

在样例一中的数据 1 2 4 1 3 4,可以挑 1,2,3,4(位置是 1,2,5,6),长度是 4。也可以挑 1,2,4,但只有 3,所以最长是 4

而我们要做的是这样一件事:

不管整个序列怎么组成,我只管'以当前这个数字作为结尾最长能有多长。要想知道它,我就扫描它前面所有比它小的数字,看谁结尾的长度最大,我就在谁后面接上自己。

所以,接下来,我们要一个a数组用来存数据,dp数组用来进行动态规划的部分。

  • 每个元素本身可以单独构成一个长度为 1 的子序列,所以 dp[i] = 1
  • 对于每个 i i i枚举所有 j < i j<i j<i
  • 如果 a j < a i a_j<a_i aj<ai那么就可以把a[i]接在a[j]结尾的最长上升子序列后面,形成一个新的更长的子序列。
  • 因此状态转移方程为:
    d p i = max ⁡ ( 1 , max ⁡ j < i a j < a i ( d p j + 1 ) ) dpi = \max\left(1, \max_{\substack{j < i \\ aj < ai}} (dpj + 1)\right) dpi=max(1,maxj<iaj<ai(dpj+1))

到此,题目分析结束,可以开始写代码了。

AC代码
cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

int n;
int a[5005];   // 原始序列
int dp[5005];  // dp[i] 表示以 i 结尾的最长上升子序列长度

int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }

    int ans = 0;
    for (int i = 1; i <= n; i++) {
        dp[i] = 1;                         // 以自身结尾,长度至少为 1
        for (int j = 1; j < i; j++) {
            if (a[j] < a[i]) {             // 满足上升条件
                dp[i] = max(dp[i], dp[j] + 1);
            }
        }
        ans = max(ans, dp[i]);             // 更新全局最优解
    }

    cout << ans;
    return 0;
}

接下来,我们就要学会如何使用这个模板了,

下面一道题是dp的应用:

例题二、B3637 合唱队形

P1091 NOIP 2004 提高组 合唱队形

题目描述

n n n 位同学站成一排,音乐老师要请其中的 n − k n-k n−k 位同学出列,使得剩下的 k k k 位同学排成合唱队形。

合唱队形是指这样的一种队形:设 k k k 位同学从左到右依次编号为 1 , 2 , 1,2, 1,2, ... , k ,k ,k,他们的身高分别为 t 1 , t 2 , t_1,t_2, t1,t2, ... , t k ,t_k ,tk,则他们的身高满足 t 1 < ⋯ < t i > t i + 1 > t_1< \cdots <t_i>t_{i+1}> t1<⋯<ti>ti+1> ... > t k ( 1 ≤ i ≤ k ) >t_k(1\le i\le k) >tk(1≤i≤k)。

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

输入格式

共二行。

第一行是一个整数 n n n( 2 ≤ n ≤ 100 2\le n\le100 2≤n≤100),表示同学的总数。

第二行有 n n n 个整数,用空格分隔,第 i i i 个整数 t i t_i ti( 130 ≤ t i ≤ 230 130\le t_i\le230 130≤ti≤230)是第 i i i 位同学的身高(厘米)。

输出格式

一个整数,最少需要几位同学出列。

输入输出样例 #1
输入 #1
复制代码
8
186 186 150 200 160 130 197 220
输出 #1
复制代码
4
说明/提示

对于 50 % 50\% 50% 的数据,保证有 n ≤ 20 n \le 20 n≤20。

对于全部的数据,保证有 n ≤ 100 n \le 100 n≤100。

题目分析

由题得"他们的身高满足 t 1 < ⋯ < t i > t i + 1 > t_1< \cdots <t_i>t_{i+1}> t1<⋯<ti>ti+1> ... > t k ( 1 ≤ i ≤ k ) >t_k(1\le i\le k) >tk(1≤i≤k)",那意思是这个序列是先上升,后下降的。那我们很容易就能看出我们需要正着算一遍序列,再反着算一遍序列,再给他们拼在一起,减去中间那个计算重复的,那就是答案。

思路理清后,我们就可以开始写代码了:

AC代码
cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

int n;
int a[5005];
int dp1[5005];
int dp2[5005];

int main()
{
	cin >> n;
	
	for (int i = 1; i <= n; i++)
	{
		cin >> a[i];
	}
	
	// 上升
	for (int i = 1; i <= n; i++)
	{
		dp1[i] = 1;
		for (int j = 1; j < i; j++)
		{
			if (a[j] < a[i])
			{
				dp1[i] = max(dp1[i], dp1[j] + 1);
			}
		}
	}
	
	// 下降
	for (int i = n; i >= 1; i--)
	{
		dp2[i] = 1;
		for (int j = n; j > i; j--)
		{
			if (a[j] < a[i])
			{
				dp2[i] = max(dp2[i], dp2[j] + 1);
			}
		}
	}
	
	int ans = 0;
	for (int i = 1; i <= n; i++)
	{
		ans = max(ans, dp1[i] + dp2[i] - 1);
	}
	
	cout << n - ans << endl;
}
相关推荐
c238569 小时前
MySQL 基础用法(上):库表管理与数据增删改
c语言·数据库·c++·mysql
李永奉9 小时前
C语言-嵌入式开发C语言常用预处理指令合集
c语言·开发语言
光源【时光寸寸又逢君】10 小时前
第三方平台预览我方级联监控点串流问题排查
c++·iot
上海安当技术10 小时前
敏感数据怎么防拖库?信封加密(DEK+KEK 二层密钥)架构设计与 Java 实战
java·开发语言·python
我是唐青枫10 小时前
Java JCommander 实战详解:用注解解析命令行参数
java·开发语言
大鹏说大话10 小时前
C++ 内存布局详解:类、虚函数、虚表底层原理
java·开发语言
键盘会跳舞10 小时前
C++:容器适配器 queue 的源码级拆解
开发语言·c++·queue
YuforiaCode11 小时前
第十六届蓝桥杯 2025 C/C++组 数列差分
c语言·c++·蓝桥杯
千里之行,始于足下sanhai11 小时前
P8686 [蓝桥杯 2019 省 A] 修改数组 - 数字魔法大冒险 题解
c++·算法·动态规划