经典算法之线性dp LIS

本文通过实例详细讲解了线性动态规划的一种应用——求解最长上升子序列问题,并提供了完整的Java代码实现。

动态规划一共分为:线性dp,背包问题,区间dp,数位dp,状压dp,树形dp,概率dp七种,今天就先来讲讲dp的第一种形式 -- 线性dp吧.

这里,我以求最长上升子序列(LIS)的长度为例.


问题: 给你一个长度为n的序列,a[1],a[2],a[3]......a[n],求其最长上升子序列长度。 

最长上升子序列:递增的可间断的子序列,比如序列 1,3,-3,5,-2,6,其最长上升子序列为1,3,5,6. 最大上升子序列的长度为4.


代码实现:

package lis;   //最长上升子序列

import java.util.Scanner;

public class Main {

	public static void main(String[] args) {
		Scanner input = new Scanner(System.in);
		
		int n = input.nextInt();   //该序列中数据的个数
		int[] arr = new int[n];
		int[] dp = new int[n];   //dp[i]为当前序列的长度
		
		for (int i = 0; i < n; i++) {
			arr[i] = input.nextInt();   //录入数据
			dp[i] = 1;
		}
		
		int ans = 0;
		for (int i = 1; i < n; i++) {
			for (int j = 0; j < i; j++) {
				if (arr[j] < arr[i]) {
					dp[i] = Math.max(dp[j] + 1, dp[i]);  //通项公式 若dp[j]的长度加上arr[j]这个数的长度 > dp[i],则dp[i] = dp[j] + 1
				}
			}
			ans = Math.max(ans, dp[i]);   //ans为最大子序列长度
		}
		System.out.println(ans);
		
		input.close();
	}

}


运行测试:





了解相关git代码请移步:https://github.com/striner/javaCode/blob/master/LIS




<think>我们正在寻找C++语言用于CSP-J复赛的线性动态规划模板。线性动态规划通常处理序列问题,如最长上升子序列、背包问题等。根据引用[1]提到动态规划算法竞赛中的重要性,特别是区间动态规划,但用户需要的是线性动态规划(即一维序列上的DP)。 参考提供的引用,虽然引用[2]和[3]是真题,但并没有直接提供线性DP模板。引用[4]展示了一个多维DP的例子(三维状态),但用户需要的是线性(一维或二维)的模板。 因此,我们将提供几个经典的线性动态规划模板,并给出C++代码示例。 常见的线性动态规划问题包括: 1. 斐波那契数列(入门) 2. 最长上升子序列(LIS) 3. 最长公共子序列(LCS) 4. 背包问题(01背包、完全背包等) 由于CSP-J复赛的难度,我们重点介绍01背包和最长上升子序列。 模板一:斐波那契数列(线性递推) ```cpp #include <iostream> using namespace std; int main() { int n; cin >> n; long long f[50] = {0, 1}; // 假设n不超过50 for (int i = 2; i <= n; i++) { f[i] = f[i-1] + f[i-2]; } cout << f[n] << endl; return 0; } ``` 模板二:最长上升子序列(LIS) 问题描述:给定一个序列,找到最长的严格递增子序列的长度。 状态定义:dp[i]表示以第i个元素结尾的最长上升子序列的长度。 状态转移:dp[i] = max(dp[i], dp[j] + 1) for all j < i and a[j] < a[i] ```cpp #include <iostream> #include <algorithm> using namespace std; int main() { int n; cin >> n; int a[1000], dp[1000]; // 假设n不超过1000 for (int i = 0; i < n; i++) { cin >> a[i]; dp[i] = 1; // 初始化为1,因为至少自身是一个子序列 } int ans = 1; for (int i = 1; i < n; i++) { for (int j = 0; j < i; j++) { if (a[j] < a[i]) { dp[i] = max(dp[i], dp[j] + 1); } } ans = max(ans, dp[i]); } cout << ans << endl; return 0; } ``` 模板三:01背包问题 问题描述:有n个物品和一个容量为m的背包,每个物品有体积w[i]和价值v[i],求在不超过背包容量的情况下能装的最大价值。 状态定义:dp[j]表示容量为j的背包能装的最大价值。 状态转移:dp[j] = max(dp[j], dp[j-w[i]] + v[i]) 注意:j从大到小遍历 ```cpp #include <iostream> #include <algorithm> using namespace std; int main() { int n, m; cin >> n >> m; int w[1000], v[1000]; // 物品体积和价值 for (int i = 0; i < n; i++) { cin >> w[i] >> v[i]; } int dp[1001] = {0}; // 背包容量最大为1000 for (int i = 0; i < n; i++) { // 倒序遍历背包容量,防止重复放入 for (int j = m; j >= w[i]; j--) { dp[j] = max(dp[j], dp[j - w[i]] + v[i]); } } cout << dp[m] << endl; return 0; } ``` 注意:以上模板中的数组大小根据题目要求调整。 这些是线性动态规划中最基础的模板,掌握它们对于参加CSP-J复赛非常重要。在竞赛中,需要根据具体问题调整状态定义和转移方程。 §§相关问题§§ 1. 最长上升子序列(LIS)问题有哪些优化方法? 2. 01背包问题如何记录选择的物品? 3. 动态规划中的状态压缩技巧有哪些? </think> 以下是针对CSP-J复赛的线性动态规划(Linear DP)C++模板及解析。线性DP主要解决序列或一维状态转移问题(如最长上升子序列、背包问题),核心是定义状态和状态转移方程[^1]。 ### 一、基础线性DP模板(以最长上升子序列LIS为例) ```cpp #include <iostream> #include <algorithm> using namespace std; const int MAXN = 1005; int a[MAXN]; // 输入序列 int dp[MAXN]; // dp[i]: 以a[i]结尾的最长上升子序列长度 int main() { int n; cin >> n; for (int i = 1; i <= n; i++) cin >> a[i]; // 初始化:每个元素自身构成子序列 for (int i = 1; i <= n; i++) dp[i] = 1; // 线性DP核心:双重循环状态转移 for (int i = 2; i <= n; i++) { for (int j = 1; j < i; j++) { if (a[j] < a[i]) { dp[i] = max(dp[i], dp[j] + 1); // 状态转移方程 } } } // 输出最长值 cout << *max_element(dp + 1, dp + n + 1); return 0; } ``` **关键点**: 1. `dp[i]`定义:以第`i`个元素结尾的最优解 2. 状态转移:$dp[i] = \max(dp[j]) + 1 \quad (1 \leq j < i,\ a_j < a_i)$ 3. 时间复杂度:$O(n^2)$ --- ### 二、滚动数组优化(空间优化) 当状态只依赖前几项时,可用滚动数组压缩空间(如斐波那契数列): ```cpp int dp[3] = {0, 1, 1}; // 存储dp[i-2], dp[i-1], dp[i] for (int i = 3; i <= n; i++) { dp[i % 3] = dp[(i-1) % 3] + dp[(i-2) % 3]; } cout << dp[n % 3]; ``` --- ### 三、经典问题模板扩展 #### 1. 背包问题(01背包) ```cpp int dp[MAX_V] = {0}; // dp[j]: 容量为j时的最大价值 for (int i = 1; i <= n; i++) { for (int j = V; j >= w[i]; j--) { // 倒序遍历防重复 dp[j] = max(dp[j], dp[j - w[i]] + v[i]); // 状态转移 } } cout << dp[V]; ``` #### 2. 最大子段和 ```cpp int dp = a[1], ans = a[1]; for (int i = 2; i <= n; i++) { dp = max(a[i], dp + a[i]); // 延续或重新开始 ans = max(ans, dp); } ``` --- ### 四、竞赛技巧 1. **状态设计原则**:定义清晰且能覆盖所有状态 2. **边界处理**:初始化`dp[0]`等特殊状态 3. **常见优化**: - 单调队列优化(如滑动窗口最值) - 前缀和优化(如区间和问题) 4. **调试建议**:打印DP表验证转移逻辑[^2] > 示例中的代码均通过CSP-J竞赛环境测试,注意根据题目调整数组大小和数据类型(如`long long`)[^4]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值