经典dp问题讲解(线性dp)二

第四道题目三个串的最长公共子序列

题面内容给你三个串,求其最长公共子序列,输出最长公共子序列的方案。s<=300
首先还是那句话,子序列就是挑一些元素,然后我们回忆一下就是最长公共子序列咱们去做?
自由解决非常简单的一道题目

对于这道题目来说就是开三维数组
f【i】【j】【k】第一个串的前i个字符第二个串的前j个字符第三个串的前k个字符的最长公共子序列
然后在这里扩展一下一个东西就是我们大多数人理解的max只能去比较两个数值的大小,那么如果我们想比较多个数值中的最大数值怎么办,依然可以用max,加上一个大括号即可
用法:max({a, b, c, d, ...}) ← 注意大括号 {}

接下来就是本道题目的ac代码
 

 #include <bits/stdc++.h>
using namespace std;
const int maxn=305;
int dp[maxn][maxn][maxn];
string s1,s2,s3;
string ans;
void recover(int i,int j,int k){
	if(i==0 || j==0 || k==0)
		return;
	if(s1[i-1]==s2[j-1]&&s2[j-1]==s3[k-1]){
		recover(i-1,j-1,k-1);
		ans.push_back(s1[i-1]);
	}
	else{
		//找到最大的那个方向开始回溯
		if(dp[i][j][k]==dp[i-1][j][k]){
			recover(i-1,j,k);
		}
		else if(dp[i][j][k]==dp[i][j-1][k]){
			recover(i,j-1,k);
		}
		else{
			recover(i,j,k-1);
		}
	}
}
int main()
{
	cin>>s1>>s2>>s3;
	int n1=s1.length(),n2=s2.length(),n3=s3.length();
	memset(dp,0,sizeof(dp));
	for(int i=1;i<=n1;i++){
		for(int j=1;j<=n2;j++){
			for(int k=1;k<=n3;k++){
				if(s1[i-1]==s2[j-1]&&s2[j-1]==s3[k-1])
					dp[i][j][k]=dp[i-1][j-1][k-1]+1;
				else{
					dp[i][j][k]=max({dp[i-1][j][k],dp[i][j-1][k],dp[i][j][k-1]});
				}
			}
		}
	}
	ans="";
	recover(n1,n2,n3);
	cout<<dp[n1][n2][n3]<<endl;
	cout<<ans<<endl;
	return 0;
}

第五道题目 最大子矩阵

在一个矩阵中找到一个子矩阵,该子矩阵和最大,输出最大和即可

这道题目的暴力思想怎么去做大家可以思考一下,很简单就会死枚举这个矩形的左上顶点和右下顶点,然后用二维前缀和预处理就行了,最后不断更新这个最大数值即可,但是暴力的复杂度巨高。

然后对于这道题目我们怎么用动态规划的思想去做?显然无论这个子矩阵如何,它都会有一个上边界和一个下边界,然后我们将每一列的第i行到第j行的数值压缩成一行,然后你就会发现问题转变成了最大子串和问题

#include <bits/stdc++.h>
using namespace std;
int main()
{
	int n,m;
	cin>>n>>m;
	vector<vector<int>>matrix(n,vector<int>(m));
	for(int i=0;i<n;i++)
		for(int j=0;j<m;j++)
			cin>>matrix[i][j];
	int maxSum=INT_MIN;
	//枚举上边界
	for(int top=0;top<n;top++){
		vector<int>colSum(m,0);
		//枚举下边界
		for(int bottom=top;bottom<n;bottom++){
			//更新列和(列方向上的累加)
			for(int j=0;j<m;j++)
				colSum[j]+=matrix[bottom][j];
			//一维最大子段和
			vector<int>dp(m);
			dp[0]=colSum[0];
			maxSum=max(maxSum,dp[0]);
			for(int j=1;j<m;j++){
				dp[j]=max(colSum[j],dp[j-1]+colSum[j]);
				maxSum=max(maxSum,dp[j]);
			}
		}
	}
	cout<<maxSum<<endl;
	return 0;
}

第六道题目 拦截导弹

题面描述
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹袭击。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

输出这套系统最多拦截的导弹数和要拦截所有导弹最少要配备这种导弹拦截系统的套数。

学历这么久的线性dp问题,相比你应该能迅速理解这道题目的本质

最多能拦截的导弹数就是求最长不上升子序列长度
需要的系统套数就是求解最少的不上升子序列覆盖数

但是话又说回来了,使用dp的方法比用贪心加上二分查找的复杂度要高吗,但是我们这里2种方法的解法都会给到。

穿插知识点 Dilworth定理

属于组合数学里面的内容:强调在任何有限偏序集中,最小链划分的数量等于最大反链的大小。
结合这道题目我们来理解这里都是在讲什么东西


偏序集
一个集合+一个偏序关系(自反,反对称,传递)
这里面的这些关系在离散数学中会提及不必过多纠结把这个定理背下来,理解即可


一个子集,其中任意两个元素都是可比的(即全序子集)
在本题目中一个不上升子序列就是一个链

反链
在一个子集中,任意两个元素都是不可比的
在本题目中一个上升子序列就是一个反链

综上在这里我们想要求解最小链的划分数量就是求解最大反链的大小
即 最少系统数量 = 最长上升子序列的长度
 

此题目dp做法
 

#include <bits/stdc++.h>
using namespace std;
int main()
{
	vector<int>heights;
	int x;
	while(cin>>x){
		heights.push_back(x);
	}
	int n=heights.size();
	//最长不上升子序列
	vector<int>dp1(n,1);
	int ans1=1;
	for(int i=1;i<n;i++){
		for(int j=0;j<i;j++){
			if(heights[j]>=heights[i]){
				dp1[i]=max(dp1[i],dp1[j]+1);
			}
		}
		ans1=max(ans1,dp1[i]);
	}
	//最长上升子序列
	vector<int>dp2(n,1);
	int ans2=1;
	for(int i=1;i<n;i++){
		for(int j=0;j<i;j++){
			if(heights[j]<heights[i]){
				dp2[i]=max(dp2[i],dp2[j]+1);
			}
		}
		ans2=max(ans2,dp2[i]);
	}
	cout<<ans1<<endl<<ans2<<endl;
	return 0;
}

二分+贪心
 

#include <bits/stdc++.h>
using namespace std;
int main()
{
	vector<int>heights;
	int x;
	while(cin>>x){
		heights.push_back(x);
	}
	int n=heights.size();
	//最长不上升子序列
	vector<int>dp1;//dp1[i]表示长度为i+1的不上升子序列的末尾元素的最大数值
	for(int i=0;i<n;i++){
		//找到第一个小于当前高度的位置
		auto it=upper_bound(dp1.begin(),dp1.end(),heights[i],greater<int>());
		  if (it == dp1.end()) {
            dp1.push_back(heights[i]);
        } else {
            *it = heights[i];
        }
	}
	int ans1=dp1.size();
	//最长上升子序列
	 vector<int> dp2;  // dp2[i] 表示长度为i+1的上升子序列的末尾元素的最小值
    for (int i = 0; i < n; i++) {
        auto it = lower_bound(dp2.begin(), dp2.end(), heights[i]);
        if (it == dp2.end()) {
            dp2.push_back(heights[i]);
        } else {
            *it = heights[i];
        }
    }
    int ans2 = dp2.size();
    
    cout << ans1 << "\n" << ans2 << endl;
	r

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值