洛谷P5745 【深基附B例】区间最大和

题目链接:https://www.luogu.com.cn/problem/P5745

题目描述

给定 n 个正整数组成的数列 a1,a2,,an 和一个整数 m。求出这个数列中的一个子区间 [i, j],也就是在这个数列中连续的数字 a_i, a_{i + 1},a_{j - 1}, a_jai,ai+1,,aj1,aj,使得这个子区间的和在不超过 m的情况下最大。如果有多个区间符合要求,请输出 ii 最小的那一个。

输入格式

输入共两行。

第一行,两个整数 n, m

第二行,n 个整数1,a2,,an

输出格式

一行,三个整数,表示符合题意的区间的左端点、右端点和累加和。

输入输出样例

输入 
5 10
2 3 4 5 6
输出 
1 3 9

说明/提示

子任务 1(10分):n200 ;

子任务 2(20分):n3000 ;

子任务 3(30分):n10^5 ;

子任务 4(40分):n4×10^6 。

对于 100% 的数据,1 n4×10^6,1m10^9

解题思路:
 
看起来不难但是很容易TLE 所以我们一步一步减小时间复杂度
 

10分代码:

很容易想到枚举所有的ij,再对ij进行求和,再判断是否满足条件,满足就更新答案。

时间复杂度O(n^3)

 
#include <bits/stdc++.h>
using namespace std;
int n,m;
int a[4000001];
int ans1,ans2,ans3;
int main(){
	cin>>n>>m;
	for(int i=1; i<=n; i++){//读入
		scanf("%d",&a[i]);
	}
	for(int i=1; i<=n; i++){
		for(int j=i; j<=n; j++){//枚举i与j
			int sum=0;
			for(int k=i; k<=j; k++){//求和
				sum+=a[k];
			}
			if(sum>ans3&&sum<=m){//如果这个答案比以前的更优,则更新答案
				ans1=i;
				ans2=j;
				ans3=sum;
			}
		}
	}
	cout<<ans1<<" "<<ans2<<" "<<ans3;//输出
}

 

 

30分代码:

因为要对区间进行求和,自然就会想到前缀和来维护。

我们记sumi=a1+a2+......+ai

al+al+1+......ar=sumrsum(l1)

在读入的过程中预处理前缀和

时间复杂度:O(n^2)

代码:

#include <bits/stdc++.h>
using namespace std;
int n,m;
int a[4000001];
int sum[4000001];//用sum数组来记录前缀和
int ans1,ans2,ans3;
int main(){
    cin>>n>>m;
    for(int i=1; i<=n; i++){
        scanf("%d",&a[i]);
        sum[i]=sum[i-1]+a[i];//处理前缀和
    }
    for(int i=1; i<=n; i++){
        for(int j=i; j<=n; j++){
        int cnt=sum[j]-sum[i-1];
            if(cnt>ans3&&cnt<=m){//计算前缀和并更新答案
                ans1=i;
                ans2=j;
                ans3=cnt;
            }
        }
    }
    cout<<ans1<<" "<<ans2<<" "<<ans3;
}

60分代码:

因为所有的ai都是正整数,故sum单调递增。

所以若sumrsum(l1)>m,

因为sumr+1>sumr

sumr+1suml1>m

后面的sumr+ksuml1>m

所以就可以break了

时间复杂度:O(n^2)+小常数

 

代码:

#include <bits/stdc++.h>
using namespace std;
int n,m;
int a[4000001];
int sum[4000001];
int ans1,ans2,ans3;
int main(){
    cin>>n>>m;
    for(int i=1; i<=n; i++){
        scanf("%d",&a[i]);
        sum[i]=sum[i-1]+a[i];
    }
    for(int i=1; i<=n; i++){
        for(int j=i; j<=n; j++){
        int cnt=sum[j]-sum[i-1];
            if(cnt>ans3&&cnt<=m){
                ans1=i;
                ans2=j;
                ans3=cnt;
            }
            if(cnt>m) break;//如果比m大就可以退出了,不必要继续枚举了
        }
    }
    cout<<ans1<<" "<<ans2<<" "<<ans3;
}

100分代码:

因为sum单调递增。

所以考虑枚举i,再二分确定j

时间复杂度:O(nlogn)

代码:

 
#include <bits/stdc++.h>
//#include <windows.h>
using namespace std;
long long sum[4000001];
long long n,m;
long long a[4000001];
int ans1,ans2,ans3;
int main(){
    cin>>n>>m;
    for(int i=1; i<=n; i++){//预处理前缀和
        scanf("%d",&a[i]);
        sum[i]=sum[i-1]+a[i];
    }
    for(int i=1; i<=n; i++){
        int l=i,r=n;
        while(l<=r){//二分确定最优的j
            int mid=(l+r)/2;
            if(sum[mid]-sum[i-1]>m){
                r=mid-1;
            } else {
                l=mid+1;
            }
        }
        if(sum[r]-sum[i-1]<=m){//对于当前的i来说,这个j已经是最优的了,所以更新答案
            if(sum[r]-sum[i-1]>ans3){
                ans1=i;
                ans2=r;
                ans3=sum[r]-sum[i-1];
            }
        }
    }
    cout<<ans1<<' '<<ans2<<' '<<ans3;  
    return 0;    
}

 

posted @ 2022-03-28 15:41  LuoJMeng  阅读(434)  评论(0)    收藏  举报