首先看一个最大连续子序列和的问题,如下:
给定一个整数数列A1,A2,。。。An(n<=10000),求i,j(1<=i<j<=n),使得Ai+...+Aj最大,输出这个最大和,并且输出这个最大连续子序列的首尾元素。
输入占两行,第一行为输入的元素的数量,第二行为输入的元素。
输出占一行,第一个数是最大连续子序列的和,第二个元素为最大连续子序列的首元素,第三个为最大连续子序列的末尾元素,且元素间用一个空格隔开,最后一个元素后无空格。如果输入的序列所有元素均为负数,则规定最大连续子序列的和为0,输出的首尾元素即为整个输入序列的首位元素。
例如:
样例1:
Input:
10
-10 1 2 3 4 -5 -23 3 7 -21
Output:
10 1 4
样例2:
Input:
4
-1 -2 -3 -4
Output:
0 -1 -4
样例3:
Input:
10
-10 1 2 3 4 -5 -23 4 7 -21
Output:
11 4 7
可使用动态规划算法的问题特征就是其存在重叠的子问题和最优子结构。
在该例子中,重叠的子问题就是:若要求以A[i]结尾的连续子序列的最大和,就要求以A[i-1]结尾的连续子序列的最大和。如:求以A[4]结尾的连续子序列的最大和,就要求A[3]结尾的连续子序列的最大和;求以A[5]结尾的连续子序列的最大和也要求一次A[3]结尾的连续子序列的最大和。这就出现重复求A[3]结尾的连续子序列的最大和,即重叠的子问题。
最优子结构在该例中就是所要求的结果,即最大连续子序列和。
对于该问题,假设以A[i]结尾的最大连续子序列为dp[i],那么存在下列两种情况:
1,最大连续子序列为A[i]本省,即只存在一个元素,此时dp[i]=A[i];
2,最大连续子序列是A[i]+dp[i-1],其中dp[i-1]为A[i-1]结尾的最大连续子序列和,此时d[i]=A[i]+dp[i-1]。
因为以A[i]结尾的最大连续子序列的末尾元素只能为A[i],故仅存在上述两种情况,故可总结出状态转移方程:
dp[i]=max(A[i],A[i]+dp[i-1])
该例中还要求输出的第二个元素为最大连续子序列的首元素,第三个为最大连续子序列的末尾元素。因此还需在求解最大连续子序列的过程中保存首尾元素。
设数列s[i]保存以A[i]结尾的最大连续子序列的起始元素的下标。
数组s[N]初始化为0,则在状态转移方程的两种状况下,s[i]的值也分为两种状况:
1,最大连续子序列为A[i]本省,即只存在一个元素,此时dp[i]=A[i],s[i]=i;
2,最大连续子序列是A[i]+dp[i-1],其中dp[i-1]为A[i-1]结尾的最大连续子序列和,此时d[i]=A[i]+dp[i-1],s[i]=s[i-1];
使用情况和状况意在区分两者针对的对象不同,以下代码注释出也根据此标注。
由上分析,编写程序:
#include<iostream>
#include<algorithm>
using namespace std;
const int N=10001;
int A[N],dp[N]; //数组A保存输入的序列,数组dp保存以A[i]结尾的最大连续子序列的和
int s[N]={0}; //数组s保存以A[i]结尾最大连续子序列的起始元素,初始化为0
int main(){
int K;
cin>>K; //输入首行元素,为序列的元素个数
int flag=0; //flag用以表示序列是否全为负数
for(int i=0;i<K;++i){
cin>>A[i]; //输入序列,用数组A保存
if(A[i]>=0)
flag=1; //序列中存在非负数,flag置1
}
dp[0]=A[0]; //设置边界,以第一个元素为最大连续子序列开始递推
int re[3]; //定义数组re,re[0]保存最长子序列和,re[1]保存元素,re[2]保存末尾元素
for(int i=1;i<K;++i){
dp[i]=max(A[i],A[i]+dp[i-1]); //判断是情况1还是情况2,得出dp[i]的值
if(A[i]>A[i]+dp[i-1]){ //判断是状况1还是状况2,得出s[i]的值
s[i]=i;
}
else{
s[i]=s[i-1];
}
}
if(flag==0){ //flag为0,序列全为负数
re[0]=0;
re[1]=A[0];
re[2]=A[K-1];
}
else{ //否则,序列存在非负数
int k=0; //设最大连续子序列以A[k]结尾,初始化k=0
for(int i=1;i<K;++i){ //遍历数组dp,找出最大连续子序列和
if(dp[i]>dp[k]){
k=i;
}
}
re[0]=dp[k]; //re[0]保存最大连续子序列和
re[1]=A[s[k]]; //re[1]保存最大连续子序列的首元素
re[2]=A[k]; //re[2]保存最大连续子序列的末尾元素
}
for(int i=0;i<3;++i){ //按规定格式输出
if(i==0)
cout<<re[i];
else{
cout<<" ";
cout<<re[i];
}
}
cout<<endl;
return 0;
}
读者可自行根据文字分析和例题编写程序,本人写的程序不够高效,有可改进之处,希望读者可以写出更加简洁高效的代码。