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

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

题目描述

给定 \(n\) 个正整数组成的数列 \(a_1, a_2, \cdots, a_n\) 和一个整数 \(m\)。求出这个数列中的一个子区间 \([i, j]\),也就是在这个数列中连续的数字 \(a_i, a_{i + 1}, \cdots, a_{j - 1}, a_j\),使得这个子区间的和在不超过 \(m\) 的情况下最大。如果有多个区间符合要求,请输出 \(i\) 最小的那一个。

输入格式

输入共两行。

第一行,两个整数 \(n, m\)

第二行,\(n\) 个整数 \(a_1, a_2, \cdots, a_n\)

输出格式

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

输入输出样例 #1

输入 #1

5 10
2 3 4 5 6

输出 #1

1 3 9

说明/提示

子任务 1(10分):\(n\le 200\)

子任务 2(20分):\(n\le 3000\)

子任务 3(30分):\(n\le 10^5\)

子任务 4(40分):\(n\le 4\times 10^6\)

对于 \(100\%\) 的数据,\(1 \leq n \leq 4 \times 10^6\)\(1 \leq m \leq 10^9\)\(0 \leq a_1, a_2, \cdots, a_n \leq 10^5\)


思路 \(1.\)

最直接的思路就是枚举所有子区间的头尾 \(i\)\(j\) ,然后对 \(i\)\(j\) 里面的所有数字累加起来求和,再判断是否在不大于 \(M\) 的情况下最大。时间复杂度为 \(O(n^3)\) ,只能通过子任务 \(1\) 获得 \(10\)分。

思路 \(2.\)

枚举每次区间,计算区间和是很浪费时间的。使用前缀和可以快速求出区间 \([i,j]\) 的序列和。可以枚举头尾 \(i\)\(j\) 求和判断。时间复杂度为 \(O(n^2)\) ,可以获得 \(30\) 分。

思路 \(3.\)

先对 \(a\) 数组求前缀和,然后固定左端点 \(i\) ,找最后一个右端点 \(j\) ,使得 \(\sum_{n=i}^{j}{a_n}\leq M\) ,并且 \(\sum_{n=i}^{j+1}{a_n}>M\) 。因为求过前缀和的 \(a\) 数组肯定是从小到大的,所以这个问题具有单调性,可以使用二分查找右端点 \(j\) 。时间复杂度为 \(O(n \log n)\) ,可以获得全部分数。代码如下:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned u;
const int N=4e6+5;
int n,m,x,y,t,l,r,mid,ans;
ll mx,a[N];
int find(int k){
	l=k,r=n;
	while(l<=r){
		mid=(l+r)>>1;
		if(a[mid]-a[k-1]<=m){
			ans=mid;
			l=mid+1;
		}
		else{
			r=mid-1;
		}
	}
	return ans;
}//最好写的二分
int main(){
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		a[i]+=a[i-1];//求前缀和
	}
	for(int i=1;i<=n;i++){
		t=find(i);
		if(a[t]-a[i-1]>mx){
			x=i;
			y=t;
			mx=a[t]-a[i-1];
		}
	}
	cout<<x<<' '<<y<<' '<<mx;
	return 0;
}

思路 \(4.\)

还可以更快!维护一个队列(其实维护的是队列的头尾指针,不需要额外建一个队列),依次将 \(a\) 数组中的数入队,直到加入下一个数字会导致队列中的数字和超过 \(M\) 为止。这里的队列就是一个子区间,记录这个子区间的所有元素和并更新答案。弹出队首,然后继续入队、记录答案······不断这么操作,直到队列为空为止。代码如下:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned u;
const int N=4e6+5;
int n,m,l=1,r,x,y,s,mx,a[N];
int main(){
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++)cin>>a[i];
	while(l<=n){
		while(s+a[r+1]<=m&&r<n){
			s+=a[++r];
		}//能入队就一直入队
		if(s<=m&&s>mx){
			mx=s;
			x=l;
			y=r;
		}//记录答案
		s-=a[l++];//弹出队首
	}
	cout<<x<<' '<<y<<' '<<mx;
	return 0;
}
posted @ 2025-08-17 10:47  Wenze_Li  阅读(39)  评论(0)    收藏  举报