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;
}

浙公网安备 33010602011771号