本题用暴力解法时间复杂度高达O(n^3),会超时,因此找一种合适的方法来简化问题;
最优子序列是个序列,那么必然含有起始位置和终止位置,我们以每个a[i]作为研究对象,研究以i为终止位置的最优子序列,得出唯一确定的以a[i]为结尾的最优子序列,每个a[i]有一个以他为结尾的最优子序列,再便利所有的最优子序列,找出最优子序列里的最优为最终结果;
本题对最优的定义是和数最大(Max Sum);定义数组a[]用来接收一组测试用例的所有数字;我们定义结构体数组dp[],dp[i]用来记录以a[i]为结尾的最大子序列的信息,dp[i].x用来记录最大子序列起始位置,dp[i].val用来记录最大子序列的和。
易知:dp[0].val=a[0],dp[0].x=0(以a[0]为结尾的序列只有一个,即a[0]本身,因此dp[0]记录的是序列(a[0])的和and位置);
通过dp[0]推导dp[1]、dp[2]、dp[3]、、、、、、dp[n-1];
推导思路:重点:dp[0]是以a[0]为结尾的最大子序列的一个记录,dp[1]必然含有a[1](因为dp[i]本身的含义是以a[i]为结尾的最大子序列,不含a[1]就不是以a[i]为结尾的序列了),dp[2]必然含有a[2]、、、、、、
首先,推dp[1],已知dp[0]是以a[0]为结尾的最大子序列,将dp[0]链接a[1],求dp[1],这个如何思考呢;
1 前面的序列和>0,a[1]>0,此时的情况记作++,此时序列无疑是越加越大的( dp[0]+a[1]>a[1] ),以a[1]为结尾的序列(dp[1])必然是前面的最大序列加上a[1];
2 前面的序列和>0,a[1]<0,此时为+-,此时序列无疑是越加越小的,但是仍然必须加上a[1],因为dp[1]规定了必须以a[1]为结尾,此时对于dp[1],因为 dp[0]+a[1]>a[1] ,所以dp[1]不取a[1]取dp[0]+a[1];
3 -+,此时dp[0]<0,既然我们要找一个以a[1]为结尾的最大子序列,a[1]本身就比a[1]+dp[0]大( dp[0]+a[1]<a[1] ),那么这个序列(dp[1])只取a[1]就够了,不需要加上dp[0](加上前面的序列会越加越小,不是最大取法);
4 --,既然都是负数,那必然越加越小,即( dp[0]+a[1]<a[1] ),同上,取a[1]为dp[1]唯一值(此时可能有人会懵:俩都是负的,万一dp[0]>a[1]呢,那按最大的取不应该取dp[0]而抛弃a[1]吗,这就回到了重点:dp[1]必然含有a[1](因为dp[i]本身的含义是以a[i]为结尾的最大子序列),所以dp[1]只能取a[1]抛弃前面的dp[0]);
从以上总结出规律:dp[0]+a[1]>a[1]时,取dp[0]+a[1]为dp[1];dp[0]+a[1]<=a[1]时,取a[1]为dp[1];再从dp[0]、a[1]、dp[1]推广到dp[i-1]、a[i]、dp[i],得到状态转移方程:dp[i]=max(dp[i-1]+a[i],a[i]);
本题的具体思路都来源这篇博客 ,只不过将代码改成了C++的模式
#include<iostream>
using namespace std;
const int MAXN = 100000;
struct dp
{
int x,val;//x代表起始位置 而val则代表的是前i个数的最优的和的数值
};
int main()
{
int num,i,j,q = 1;
cin>>num;
while(num--)
{
int n,a[MAXN];
cin>>n;
for(i = 0;i<n;i++)
cin>>a[i];
dp dp[MAXN];
//挨个输入之后
dp[0].x = 0;//起始位置为0
dp[0].val = a[0];
for(i =1;i<n;i++)
if(dp[i -1].val+a[i]>=a[i])//
{
dp[i].x = dp[i-1].x;
dp[i].val = dp[i -1].val+a[i];
}
else
{
dp[i].x = i;//这个起始位置就是这个本身
dp[i].val = a[i];
}//最优解目前已经找到
int c,b,max = -1001;//这里设置最小的值是-1001
for(i = 0;i<n;i++)
{
if(max<dp[i].val)
{
max = dp[i].val;
c = dp[i].x;
b = i;
}
}
cout<<"Case "<<q++<<":"<<endl;
cout<<max<<" "<<c+1<<" "<<b+1<<endl;
if(num!=0) cout<<endl;
}
return 0;
}
其实这一题也可以不用动态规划,因为这里的每一个子序列都必须使用最后一个值,并那最后一个值和之前的值进行比较,从而可以得出一个最大的值,时间复杂度都是一样的,O(N)。
#include<iostream>
using namespace std;
int main(){
int n,m;
cin>>n;
int t=1;
while(t<=n){
cin>>m;
int a;
int sum=0,begin=0,end=0;
int tempbegin=1;//临时子序列首元素
int max_sum=-1001; //这里最大和的赋初值为-1001 的作用就是能够确保将输入的第一个任何大小的数(在[-1000,1000]之间的数)都能够执行下叙的第一个if语句。
for(int i=1;i<=m;i++){
cin>>a;
sum+=a;
if(sum>max_sum){
max_sum=sum;
end=i;
begin=tempbegin;
}
if(sum<0){
tempbegin=i+1;
sum=0;
}
}
cout<<"Case "<<t<<":"<<endl<<max_sum<<" "<<begin<<" "<<end<<endl;
if(t<n)cout<<endl;
t++;
}
return 0;
}