动态规划

动态规划是一种用于解决最优化问题的算法,通过记录子问题的最优解来求解全局最优。本文介绍了动态规划的基本步骤,并通过最大连续子序列和、最长上升子序列和最长公共子序列这三个经典问题的解析及代码实现,帮助读者深入理解动态规划的思路和应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

动态规划是一种用来解决最优化问题的算法思想,其会记录下子问题的最优解,综合子问题的最优解得到最优解。
上面有两个关键,划分子问题和记录子问题,其中划分子问题有一个重要的状态转移方程。
一般解决步骤如下:
1.确定维数,确定dp数组。
2.每一维采用下面表述,为i或者前i。
3.之后问题可以描述为dp[i]为恰好为i(或者前i)的XXX(问题描述),然后思考dp[i-1]得出状态转移方程。

动态规划的题目主要可能得多看习题领悟,这里针对典型例题来领悟动态规划的过程。
最大连续子序列和
问题:给定一个序列(整数或浮点数),求出其中连续的子序列和最大的那一个。
例:序列{-10 1 2 3 4 -5 -23 3 7 -21},其最大的连续子序列为{1 2 3 4}或{3 7},最大和为10。

首先涉及到一个序列,应该是一维问题,应该可以用一个一维数组来记录子问题的最优解,第二步dp[i]表述恰好为i时在最大连续和(前i肯定不合适),第三步思考dp[i-1]和dp[i] 的关系,如果dp[i-1]<0,显然dp[i]=a[i],否则加上前面的dp[i-1]dp[i]肯定更大,于是状态转移方程得出。最后问题思考的序列完找出dp[i]最大的即可。

附代码如下:

#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=10001;
int dp[maxn],a[maxn];//dp[i] 表示结尾恰好为i时最大和 


int main(){
	int n;
	while(scanf("%d",&n),n){
		fill(dp,dp+n+1,-1);
		for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
		int max=-100000,k=-1;
		
		for(int i=1;i<=n;i++)
		{
			if(dp[i-1]>0)dp[i]=dp[i-1]+a[i];  //状态转移 
			else dp[i]=a[i];
			if(max<dp[i])        //更新最大值 
			{
				max=dp[i];
				k=i;
			}
		}
		
		if(max<0)printf("%d %d %d\n",0,a[1],a[n]);
		else {
		printf("%d ",max);
		for(int i=k;max!=0||dp[i-1]==0;i--)
		{
			max-=a[i];
			if(max==0&&dp[i-1]!=0)
			printf("%d ",a[i]);
		}
		printf("%d\n",a[k]);
		}
	}	
return 0;	
}

最长上升子序列
问题:给一个数组a1, a2 … an,找到最长的不下降子序列ab1<ab2<=… <abk,其中b1<b2<…bk,输出长度即可。

思考:维度应该一维,dp[i]应该表示以i结尾的最大不下降子序列长度,前面如果数字都比a[i]大那么dp[i]就为1,否则只要前面dp[j]对应a[j]<a[i],且长度+1后比dp[i]长度大,说明a[i]可以接在a[j]后面得到最大长度。

代码如下:

#include<iostream>
#include<algorithm>
using namespace std;

const int maxn=5001;
char a[maxn],dp[maxn];


int main(){
	int n,length=0;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	scanf("%d",&a[i]) ;
	fill(dp,dp+n+1,0);
	a[0]=-1;
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<i;j++) {
			if(a[j]<a[i]&&dp[j]+1>dp[i])
			dp[i]=dp[j]+1;
		}
		if(length<dp[i])length=dp[i];
	}
	printf("%d\n",length);
	return 0;
}

最长公共子序列

问题:给定两个字符串,求解这两个字符串的最长公共子序列(Longest Common Sequence)。比如字符串1:BDCABA;字符串2:ABCBDAB
则这两个字符串的最长公共子序列长度为4,最长公共子序列是:BCBA

分析:两个字符串,应该是二维,dp[i][j]应该表示为两个字符串第i,j位之前的最长公共子序列数,如果两个字符串第ij位相等,长度肯定是dp[i-1][j-1]+1,否则应该是dp[i-1][j]或者dp[ii][j-1]中的最大值。

代码如下:

#include<iostream>
#include<algorithm>
#include<string> 
using namespace std;

const int maxn=101;
string a,b;
int dp[maxn][maxn];


int main(){
  while(cin>>a>>b){
	fill(dp[0],dp[0]+maxn*maxn,0);
	int n=a.size(),m=b.size();
	for(int i=1;i<=n;i++){
	for(int j=1;j<=m;j++){
		if(a[i-1]==b[j-1])dp[i][j]=dp[i-1][j-1]+1;
		else {
			if(dp[i][j-1]>dp[i-1][j])
				dp[i][j]=dp[i][j-1];
		    else dp[i][j]=dp[i-1][j];
		}
	}
	}
	a.clear();b.clear();
	cout<<dp[n][m]<<endl;}
return 0;	
}

简单取了三个例子,此外还有最长回文子串、dag最长路、背包问题的应用。分别教会了我变换更新方式、递推和递归、打印路径,这里一定多练习。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值