LQ训练营(C++)学习笔记_常见动态规划模型

六、常见动态规划模型

1、最大字段和

1.1 概念描述

给定一个由数字组成的序列,其中连续的一段子序列称为一个子段,子段中的所有数之和称为子段和,这里只考虑非空子段,即至少包含一个元素的子段,一个序列中子段和最大的子段的子段和,为最大子段和。

1.2动态规划算法分析

对于全部是非正数的序列,最大子段和为其中元素的最大值。
对于有正数的序列,考虑以每一个点为结尾的最大子段和,这个子段一定满足其前缀和均非负,因为如果有一个前缀是负的,那么减掉这个前缀对于这个点一定更优,并且这个子段要尽量往前延伸。
我们可以只用一次扫描,记录目前统计的sum以及答案ans。当sum加上当前位置这个数还是正数的时候就继续累加sum,否则就将sum置为0 。这样就舍掉了所有前缀是负数的情况,并且保证了这个子段尽可能的长,也就是说扫描中记录的sum就是以每一个点为结尾的最大子段和,每一次如果sum比ans大就可以更新ans 。最后ans就是整体的最大子段和。

1.3 代码实现

#include<iostream>
#include<algorithm>
using namespace std;
const int inf = 0x7fffffff;
int num[101];
int main(){
	int N;
	cin>>N;//读入N和N个变量
	for(int i=0;i<N;i++){
		cin>>num[i];
	}
	int ans=-inf;//声明ans并初始化
	for(int i=0;i<N;i++){//遍历数组有没有正数,并找出最大值
		ans=max(ans,num[i])
	}
	if(ans<=0){//若ans非负,输出
		cout<<ans<<endl;
	}else{
		int sum=0;//声明sum,并初始化为0
		for(int i=0;i<N;i++){
			if(sum+num[i]<0){//如果加上后为负数,就舍弃这一段重新开始
				sum=0;
			}else{//否则将sum加上num[i]
				sum+=0;
			}
			ans=max(ans,sum);//看看是否需要更新
		}
	}
	cout<<ans<<endl;
	return 0;
}

2、最长上升子序列(LIS)

2.1 概念描述

在原序列取任意多项,在不改变他们原来数列的先后顺序,得到的序列称为原序列的子序列。最长上升子序列,就是给定的序列的最长的,数值从低到高排列的的子序列。

2.2 算法分析

所以我们需要描述一下这道题,很显然这题用一维就可以去描述了,而这题的阶段就是以第i项作为结尾端点的上升子序列(即dp[i]),而这题的状态就是第i个位置的最长上升子序列的长度,决策的话就是第i个位置是否加入要计算的最长上升子序列(a[j]<a[i],j<i这样的情况才能加入),所以我们就可以推出来他的转移方程:,其实这就是一个带有最优子结构的递推转移方程,如果满足加入最长上升子序列的条件后我们并不一定非得必须加入(因为之前还有可能有以他为结尾的更长的上升子序列)。需要注意的一点是我们需要用两层for循环来找出他的最长上升子序列长度(因为最长上升子序列不是唯一的,所以每一个起点都要去试一下)。

这是递推打表之后得到的模拟表:

在这里插入图片描述

2.3 代码实现

#include<iostream>
#include<cstring>
using namespace std;
int dp[101],a[101],n;
int LIS(){
	int ans=0;
	for(int i=1;i<=n;i++){
		dp[i]=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]);//每次内层循环结束后更新ans的值
	}
	return ans;
}
int main(){
	cin>>n;
	for(int i;i<=n;i++){
		cin>>a[i];
	}
	cout<<LIS()<<endl;
	return 0;
}

3、最长公共子序列

3.1 概念描述

给定两个序列S1和S2,求两者公共子序列S3的最长长度。

3.2 算法分析

这个题目可以按照序列的长度来划分状态,也就是S1的前i个字符和S2的前j个字符的最长公共子序列长度,记为lcs[i][j].
如果S1的第i项和S2 的第j项相同,那么S1[i]与S2[j]作为公共子序列的末尾,则
lcs[i][j]=lcs[i-1][j-1]+1
也可以不让S1[i]与S2[j]作为公共子序列的末尾,则
lcs[i][j]=max(lcs[i][j-1],lcs[i-1][j])

不难证明:

max(lcs[i][j-1],Ics[i-1][j])< Ics[i-1][j-1]+1

那么转移方程是:
在这里插入图片描述

举一个例子,两个序列 S1​ = abcfbc和S2​ = abfcab,根据转移方程可以得出 lcs[i][j]
在这里插入图片描述

3.3 代码实现

#include <iostream>
#include <cstring>
#include <string>
using namespace std;
int dp[110][110];//定义一个辅助数组
int main() 
{
    string a, b;
    memset(dp, 0, sizeof(dp));//将dp数组初始化
    cin >> a >> b;//输入两个字符串a、b
    int lena = a.size();//计算字符串a、b的长度
    int lenb = b.size();
    for (int i = 1; i <= lena; i++)
    {
        for (int j = 1; j <= lenb; j++)
        {
            if (a[i - 1] == b[j - 1])
            {
                dp[i][j] = dp[i - 1][j - 1] + 1;//dp[i][j]代表a字符串的前i个字符串组成的子串和b字符串的前j个字符串组成的子串LCS
            }
            else
            {
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
    }
    cout << dp[lena][lenb] << endl;
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值