写在前边
本篇主要记录了最大连续区间和的暴力算法和dp算法(三种写法)
以及讲解了求最大区间和的区间左右下标的方法
-----------------------------------------------------------------------------------
问题概述
这是一个经典的问题。
给定一个长度为n的序列a[1],a[2]...a[n-1],a[n]
求一个连续的子序列 a[i],a[i+1]...a[j-1],a[j],使得a[i]+a[i+1]...a[j-1]+a[j]最大。
暴力枚举法 O(n^2)
我们要求最大的连续区间和,
首先我们知道预处理出前缀和数组可以方便的求出区间和
那么我们再暴力枚举区间左右边界 找出最大的那个区间和就好了
这个暴力的做法很好想
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<=n;i++){//预处理前缀和数组
sum[i]=sum[i-1]+a[i];
}
int flag,ans=0;
for(int i=1;i<=n;i++){//暴力枚举左右边界
for(int j=i;j<=n;j++){
ans=ans>(sum[j]-sum[i-1])?ans:(sum[j]-sum[i-1]);//记录最大值
}
}
printf("%d",ans);
显然 这个n^2的方法不够优秀 难以解决数量较大的数据
所以我们需要进一步优化
动态规划解法 复杂度O(n) ---------- (三种写法)
我们让dp[ i ]等于 以a[ i ]为结束的 最大连续子段和
因为是以a[ i ]为结束且是连续子段 那么
dp[ i ] 要么就是 a[ i ]本身
要么 就是a[ i ] + 以a[ i-1 ]为结束的最大连续字段和 也就是 a[ i ] + dp[ i - 1 ]
所以 状态转移方程出来了 dp[i] = max( A[i], dp[i-1]+A[i] )
代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+7;
ll a[maxn];
ll dp[maxn];
const ll INF=8e18;
ll n,num,x;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);//输入
dp[0]=0;
for(int i=1;i<=n;i++){//状态转移方程
dp[i]=max(a[i],dp[i-1]+a[i]);
}
ll maxn=0;
for(int i=1;i<=n;i++){//遍历找最大值
maxn=max(dp[i],maxn);
}
printf("%lld\n",maxn);
}
优化常数
这个O(n)的算法 其实仔细算的话(加上输入) 是O(3n)
我们其实可以优化一下常数
输入和记录dp数组以及记录最大值都可以在一遍内完成
这个dp数组 也可以发现 扫描一遍 保留最大值就好了 那么数组也省了 一个变量就够了
最终这个写法也是求解最大连续区间和的标准解法 代码如下
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+7;
ll a[maxn];
ll n,ans,dp;
int main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
dp=max(a[i],dp+a[i]);
ans=max(dp,ans);
}
printf("%lld\n",ans);
}
还有另一种写法 也是dp的思想 复杂度O(n)
我们预处理出前缀和数组 那么sum[j]-sum[i]就是一段区间的和了
那么我们很容易得到 ans = max { sum[ j ] - min { sum[ i ] } } ( j > i )
我们只要用一个变量 动态维护一个最小前缀和就好了
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+7;
ll a[maxn],sum[maxn];
ll n,ans,minn;
int main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
sum[i]=sum[i-1]+a[i];//统计前缀和数组
ans=max(sum[i]-minn,ans);//动态维护最大区间和
minn=min(minn,sum[i]);//动态维护最小前缀和
}
printf("%lld\n",ans);
}
打印最大区间和的左右下标
例题:hdu1003
链接 传送门
这个题呢 求最大连续区间和 并且打印出来那个区间的左右边界下标
其实呢 很好写 把前边求最大区间和的算法 稍作改动就好了
优化过常数之后的动态规划解法是这样写的
核心的两句就是
dp=max(a[i],dp+a[i]);
ans=max(dp,ans);
这两句其实可以用if else语句来写
if(dp>0) dp=dp+a[i];
else dp=a[i];
if(ans>dp)ans=dp
else ans=ans;
那么这下就很好看出了
那么我们在适当的地方记录下下标就好了
s e //s为区间左端点 e为区间右端点
t //t为中间变量 用来存储改变后的区间左端点
int ans=-INF,dp=0,t=1,s=1,e=1;
if(dp>0){
dp=dp+a[i];
}
else{
dp=a[i];t=i;
}
if(ans>dp){
ans=dp;
s=t;e=i
}
hdu1003 AC代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
const int INF=0x3f3f3f3f;
int n,T,num,x;
int main(){
scanf("%d",&T);
while(T--){
num++;
scanf("%d",&n);
int ans=-INF,dp=0,t=1,e=1,s=1;
for(int i=1;i<=n;i++){
scanf("%d",&x);
if(dp>=0) dp+=x;
else dp=x,t=i;
if(dp>ans){
ans=dp;s=t;e=i;
}
}
cout<<"Case "<<num<<":"<<endl;
cout<<ans<<" "<<s<<" "<<e<<endl;
if(T!=0)cout<<endl;
}
}