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]。
于是得到状态转移方程: 。这个式子只和 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。
由此写出状态转移方程: 。其中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;
}