1. 问题描述
给定一个序列,求最大子序列和
这个问题可以用贪心算法解决,依次相加,加正数序列增大,加负数减小,如果此时序列小于0,则说明此前序列应舍去,重新从后面开始加,每次记录现在的值,判断是否最大,大于max则更新max
对了还要特判全是负数的情况
贪心代码
#include<bits/stdc++.h>
using namespace std;
int arr[1000000];
bool x=0;
int n,ans=0,mmax=-100000000;//一个非常小的数
int main()
{
cin>>n;
for(int i=1;i<=n;++i)
{
cin>>arr[i];
if(arr[i]>0)
x=1;//特判准备
}
if(x==0)//全为负数特判,直接找最大值
{
for(int i=1;i<=n;++i)
{
if(arr[i]>mmax)
{
mmax=arr[i];
}
}
cout<<mmax;
return 0;
}
for(int i=1;i<=n;++i)
{
ans+=arr[i];//累加
if(ans<0)//舍去,重新累加
{
ans=0;
}
if(ans>mmax)
{
mmax=ans;//更新最大值
}
}
cout<<mmax;
return 0;
}
同时,这题也可以动态规划
设dp[i]为以结尾的最大子序列和,则dp【i】为max{arr[i],dp[i-1]+arr[i]}依次推出,则dp【i】中的最大值为最大子序列和
dp代码
#include<bits/stdc++.h>
using namespace std;
int dp[1000000];
int n,mmax;
int main()
{
cin>>n;
for(int i=1;i<=n;++i)
{
cin>>dp[i];
}
mmax=dp[1];
for(int i=2;i<=n;++i)
{
dp[i]=max(dp[i],dp[i-1]+dp[i]);
if(dp[i]>mmax)
{
mmax=dp[i];
}
}
cout<<mmax;
return 0;
}
2.
但有时,题目会限制子序列的长度最大为m,这时,我们可以结合前缀和的知识来解决,
设s[i]为arr的前缀和,即arr1+arr2+....+arri 求出所有前缀和时间复杂度为n,则问题转化为求是s[i]-s[k],(i-k<=m) 的最大值,可以考虑单调队列,使每个s[i]进入队列一次,找到最大的子序列
#include<bits/stdc++.h>
using namespace std;
int s[1000000];
deque<int> d;
int n,m,mmax;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;++i)
{
scanf("%d",&s[i]);//输入序列
}
for(int i=2;i<=n;++i)
{
s[i]+=s[i-1];//求前缀和
}
d.push_back(1);
for(int i=2;i<=n;++i)
{
if(d.front()<i-m&&!d.empty())//相差超过m无法做差
d.pop_front();
if(s[i]-s[d.front()]>mmax&&!d.empty()) //更新max
{
mmax=s[i]-s[d.front()];
}
while(s[i]<=s[d.back()])
d.pop_back();
d.push_back(i);
}
cout<<mmax;
return 0;
}