动态规划基础

动态规划

入门问题探讨

题目传送门:leetcode 198. 打家劫舍

  • 自顶向下记忆化解决问题
    O(n)时间
class Solution {
public:
	int dfs(int x, vector<int> &dp, vector<int> &nums){
		if(x<0) return 0;
		if(dp[x]!=-1) return dp[x];
		else return dp[x]=max(dfs(x-1,dp,nums),dfs(x-2,dp,nums) + nums[x]);
	}
	int rob(vector<int>& nums){
		int n=nums.size();
		vector<int> dp( n, -1);
		return dfs( n-1, dp, nums);
	}
};
  • 自底而上递推解决问题
    递归转递推求解
    O(n)时间
class Solution {
public:
	int rob(vector<int>& nums){
		int n=nums.size();
		vector<int> dp(n+2,0);
		for(int i=0 ; i<n ; ++i){
			dp[i+2]=max(dp[i+1],dp[i]+nums[i]);
		}
		return dp[n+1];
	}
};
  • 滚动数组优化空间
    优化递推求解
    O(1)空间
class Solution {
	public:
	int rob(vector<int>& nums){
		int n=nums.size();
		int f0=0,f1=0,f2;
		for(int i=0 ; i<n ; ++i){
			f2=max(f1,f0+nums[i]);
			f0=f1;
			f1=f2;
		}
		return f2;
	}
};

摘记

动态规划的两个要求:最优子结构无后效性
最优子结构:大问题的(最优)解可以由小问题的(最优)解推出。注意在问题拆解过程中不能无限递归
无后效性:未来与过去无关,一旦得到了一个小问题的解,如何得到它的解的过程不影响大问题的求解。

动态规划的两个元素:状态转移
状态:求解过程进行到了哪一步,可以理解为一个子问题
转移:从一个状态(小问题)的(最优)解推导出另一个状态(大问题)的(最优)解的过程

最长公共子序列 LCS

LCS模板题: 百炼oj 1458:Common Subsequence

利用动态规划求解
对于字符串s(下标1 ~ n)和t(1 ~ m),开一个统计数组\(c[n][m](初始为0)\)

状态转移方程,对所有的\(1\leq i \leq n , 1 \leq j \leq m\)

\[c[i][j] = \begin{cases} c[i-1][j-1]+1, & \text {if s[i]==t[j]} \\ max(c[i-1][j],c[i][j-1]), & \text{if s[i]!=t[j]} \end{cases} \]

最后的答案即为\(c[n][m]\)
时间复杂度为\(O(nm)\)
空间可以通过滚动数组优化为 $O(2max(m, n)) $ ?


最长上升子序列 LIS

LIS 模板题: 洛谷 B3637 最长上升子序列

$O(n^2) $ 求法

void solve(){
	int n;
	cin >> n;
	vector<int> a(n), dp(n, 1);//dp 初值为 1
	int ans = 0;
	for(int i = 0; i < n; ++ i){
		cin >> a[i];
		for(int j = 0; j < i; ++ j){// 枚举之前的每一位,由前某一位dp值最大的转移过来
			if(a[j] < a[i]) dp[i] = max(dp[i], dp[j] + 1);
		}
		ans = max(ans, dp[i]);// 遍历过程中取最大答案
	}
	cout << ans << '\n';
	return ;
}

$O(n logn) $ 做法
dp[i] 表示长为 i 的子序列的末尾最小元素为 k,每次更新可以利用二分加速

void solve(){
	int n, a;
	cin >> n;
	vector<int> dp(n + 10, inf);// 初始化全部inf,代表这些位置均不可能
	for(int i = 0; i < n; ++ i){
		cin >> a;
		auto it = lower_bound(dp.begin() + 1, dp.end(), a) - dp.begin();
		dp[it] = min(dp[it], a);
	}
	cout << lower_bound(dp.begin() + 1, dp.end(), inf) - dp.begin() - 1 << '\n';
	return ;
}

相关资料
最长上升子序列的求法:我是链接

相关资料


例题

综合运用

小难,需要转换思维
$O(n^2) $ 过不了嗷!!! 因为题目具有特殊性,还是个诈骗题。。。但是有意思,再理解理解

void solve(){
	cin >> n;
	mem(b, 127);
	for(int i = 1; i <= n; ++ i) cin >> a, p[a] = i;//p 数组转换位置
	for(int i = 1; i <= n; ++ i){
		int x;
		cin >> x;
		x = p[x];
		*lower_bound(b + 1, b + 1 + n, x) = x;
	}
	cout << lower_bound(b + 1, b + 1 + n, b[0]) - b - 1 << '\n';
	return ;
}

洛谷的该题有两问
第一问,即为求最长不下降子序列的长度
显然,类似于最长上升子序列的求法,$n \log n $ 的做法即可

第二问,求需要的最少的导弹系统
法一 - 贪心二分维护导弹系统
法二 - 转化问题为求最长上升子序列

Qiansui_code

2023ACM暑假训练day 4 简单DP

2023ACM暑假训练day 11 动态规划

atc Educational DP Contest

posted on 2023-07-10 09:35  Qiansui  阅读(12)  评论(0编辑  收藏  举报