《算法笔记》读书记录DAY_54

CHAPTER_11  提高篇(5)——动态规划

11.2最大连续子序列和

我们来看最大连续自序列和问题。

题目:

给定一个数字序列A1,A2,...,An,求 i , j (1<=i<=j<=n),使得Ai+Ai+1+...+Aj最大,输出这个最大和。

输入样例:

6

-2 11 -4 13 -5 -2

输出样例:

20

思路:

如果采用暴力的做法,双循环枚举左端点和右端点,然后再累加。时间复杂度高达O(n^3)。这无法接受。

下面介绍动态规划的做法,时间复杂度为O(n)。

步骤1:令状态dp[i]表示以A[i]作为末尾的连续序列的最大和。以样例序列为例:-2 11 -4 13 -5 -2,那么

dp[0]=-2,

dp[1]=11,

dp[2]=11-4=7,

dp[3]=11-4+13=20,

dp[4]=20-5=15,

dp[5]=15-2。

通过这么设置一个dp数组,要求的最大和起始就是dp[0],dp[1],...,dp[n-1]中的最大值,下面想办法求解dp数组。

步骤2:做如下考虑,因为dp[i]要求是必须以A[i]结尾的某个连续序列,那么只有两种情况:

(1)这个最大和的连续序列只有一个元素,即只有A[i]。这种情况下最大和就是A[i]。

(2)这个最大和的连续序列有多个元素,即从前面某处A[p](p<i),一直到A[i]结尾。这种情况下最大和是dp[i-1]+A[i]。

于是得到状态转移方程dp[i]=max(A[i],dp[i-1]+A[i]) 。这个式子只和 i 与 i -1 有关,且边界为dp[0]=A[0]。由此从小到大枚举i,即可用公式得到整个dp数组,接着输出dp中的最大值即可。

参考代码:

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

const int maxn=101;

int main() {
	int A[maxn], dp[maxn];
	int n;
	scanf("%d",&n);
	for(int i=0;i<n;i++) {             //输入序列 
		scanf("%d",&A[i]);
	}
	dp[0]=A[0];                        //边界
	for(int i=1;i<n;i++) {
		dp[i]=max(A[i],A[i]+dp[i-1]);  //状态转移方程 
	}
	int k=0;
	for(int i=0;i<n;i++) {             //寻找数组中的最大值 
		if(dp[i]>dp[k])
			k=i;
	}
	printf("%d",dp[k]);
	return 0;
}

此处顺便介绍无后效性概念。状态的无后效性是指:当前状态记录了历史信息,一旦当前状态确定就不会再改变,且未来的决策只能再已有的若干个状态上进行,历史信息只能通过已有的状态去影响未来的决策。对于动态规划,必须设计一个无后效性的状态以及相应的状态转移方程。

11.3最长不下降子序列(LIS)

我们来看最长不下降子序列(LIS)问题。

题目:

在一个数字序列中,找到一个最长的子序列(可以不连续),使得这个子序列是不下降(非递减)的。

输入样例:

8

1 2 3 -9 3 9 0 11

输出样例:

6     // 即1 2 3 3 9 11

思路:

如果采用暴力方法来枚举,即对于每种元素有取和不取两种选择,然后判断是否为非递减序列,如果是则继续向后枚举,这样做的时间复杂度高达O(2^n)。

下面采用动态规划的做法,时间复杂度为O(n^2)。

设置dp数组,令dp[i]表示以A[i]结尾的LIS长度。那么对于A[i]来说有两种可能

(1)如果存在A[i]之前的元素A[j](j<i),使得A[j]<=A[i]且dp[j]+1>dp[i](即把A[i]跟在以A[j]结尾的LIS后面时能比当前以A[i]结尾的LIS长度更长),那么就把A[i]跟在以A[j]结尾的LIS后面,形成一条更长的LIS,即令dp[i]=dp[j]+1。

(2)如果A[i]之前的元素都比A[i]大,那么A[i]只好自己形成一条LIS,其长度为1。

由此写出状态转移方程dp[i]=max(1,dp[j]+1) 。其中j=1,2,...,i-1并且A[j]<A[i]。状态转移方程中隐含了边界:dp[i]=1(1<=i<=n)。

因此,只要让i从小大到大遍历求解dp数组,然后输出dp中的最大值即可。

参考代码:

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

const int maxn=101;

int main() {
	int A[maxn], dp[maxn];
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++) {          //输入序列 
		scanf("%d",&A[i]);
	}
	int ans=-1;                      //记录最大的dp[i]
	for(int i=1;i<=n;i++) {          //按顺序计算dp[i]的值 
		dp[i]=1;                     //边界初始条件 
		for(int j=1;j<i;j++) {
			if(A[i]>A[j]&&dp[j]+1>dp[i])
				dp[i]=dp[j]+1;       //状态转移方程 
		}
		ans=max(ans,dp[i]);
	}
	printf("%d",ans);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/jgsecurity/article/details/121242997