解题思路
用到的知识点:st表+堆
思维过程
按照题目的要求,我们要求所有长度为[l,r]的子区间中最大的k个,首先,我们不可能遍历所有的子区间,因为那是O(n^2)的,我们考虑贪心
先考虑k==1时:
当k=1,我们只需要找最大的一个子区间,但是,这也要遍历所有子区间,于是考虑如何用较小的时间找出最大的子区间,由于题目的n为500000,所以一般来说,存在O(n)或者O(nlogn)遍历子区间的方法,而且很可能是O(nlogn)
首先,通过预处理前缀和数组sum,我们可以在O(1)的时间得到一段区间[i,i+k]的和,即sum[i+k]-sum[i-1]
然后,我们发现以i为起点的所有子区间中最大的那个是[i,i+l-1] U RMQ([i+l,i+r-1])(区间[i+l,i+r-1]中最大的前缀和),用st表解决RMQ问题,一次RMQ的复杂度为O(1),而i从1循环到n-l+1就能找出所有子区间中最大的,st表预处理的复杂度是O(nlogn),问题解决了!
再考虑k>1:
首先,最大的子区间一定是上一问得到的答案,设最大的子区间是以i为起点,t为终点(i+l<=t && t<=i+r-1),那么次大的子区间无非两种情况:
1.不以i为起点的子区间
2.以i为起点,不以t为终点的子区间
那么,我们只需要在找最大的子区间时,把第一问中那些不同起点的最大子区间都入堆,同时把[i,i+l-1] U RMQ([i+l,i+t-1]) 的区间 以及 [i,i+l-1] U RMQ([i+t+1,i+r-1])入堆,然后每次取堆顶,并将堆顶分裂的子区间入堆即可。
总的复杂度:O(nlogn)
注意事项:
1.如何找到那个t(区间断点)
这个简单,只有稍微修改一下st表的代码即可:
//st表部分,用于求[i+l,i+r-1]区间内前缀和的最大值
int f[mx][19];//f[i][j]表示从i开始到i+(2^k)-1区间的最大值
int pos[mx][19];//pos[i][j]表示最大值的位置
void init(){
for(int j=1;(1<<j)<=n;++j)
for(int i=1;i+(1<<j)-1<=n;++i){
if(f[i][j-1]<f[i+(1<<(j-1))][j-1]){
f[i][j]=f[i+(1<<(j-1))][j-1];
pos[i][j]=pos[i+(1<<(j-1))][j-1];
}
else{
f[i][j]=f[i][j-1];
pos[i][j]=pos[i][j-1];
}
}
}
int query(int l,int r){//查找最大值的位置
int len=int(log(r-l+1)/log(2));
return f[l][len]>f[r-(1<<len)+1][len]?pos[l][len]:pos[r-(1<<len)+1][len];
}2.分裂区间时请注意,不要超过区间的边界,如果到了边界,就不要入堆
完整代码
#include<iostream>
#include<cmath>
#include<cstdio>
using namespace std;
const int mx=505050;
inline int Read(){
int x=0,f=1;
char c=getchar();
while(c>'9'||c<'0')f=c!='-'?1:-1,c=getchar();
while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();
return x*f;
}
int n,k,l,r;
//st表部分,用于求[i+l,i+r-1]区间内前缀和的最大值
int f[mx][19];//f[i][j]表示从i开始到i+(2^k)-1区间的最大值
int pos[mx][19];//pos[i][j]表示最大值的位置
void init(){
for(int j=1;(1<<j)<=n;++j)
for(int i=1;i+(1<<j)-1<=n;++i){
if(f[i][j-1]<f[i+(1<<(j-1))][j-1]){
f[i][j]=f[i+(1<<(j-1))][j-1];
pos[i][j]=pos[i+(1<<(j-1))][j-1];
}
else{
f[i][j]=f[i][j-1];
pos[i][j]=pos[i][j-1];
}
}
}
int query(int l,int r){//查找最大值的位置
int len=int(log(r-l+1)/log(2));
return f[l][len]>f[r-(1<<len)+1][len]?pos[l][len]:pos[r-(1<<len)+1][len];
}
//大根堆部分
struct Node{
int s;//从s开始连续l个
int t;//区间断点
int l;//右端点最小值
int r;//右端点最大值
int val;//区间和
bool operator > (const Node &b){
return val>b.val;
}
void swap(Node &a,Node &b){
Node t=a;
a=b;
b=t;
}
};
struct Heap{
int size;
Node h[mx*2];//删一个点加两个点,最多2*k
void push(int s,int t,int l,int r,int val){
Node a;
a.s=s,a.t=t,a.l=l,a.r=r,a.val=val;
push(a);
}
void push(Node x){
h[++size]=x;
int now=size;
int fa=now>>1;
while(fa>=1){
if(h[now]>h[fa]){
swap(h[now],h[fa]);
now=fa;
fa=now>>1;
}
else break;
}
}
void pop(){
swap(h[1],h[size]);
--size;
int now=1;
int son=now<<1;
while(son<=size){
if((son|1)<=size&&h[son|1]>h[son])son|=1;
if(h[son]>h[now]){
swap(h[son],h[now]);
now=son;
son=now<<1;
}
else break;
}
}
Node top(){
return h[1];
}
bool empty(){
return !size;
}
}heap;
int main(){
n=Read(),k=Read(),l=Read(),r=Read();
for(int i=1;i<=n;++i)f[i][0]=f[i-1][0]+Read(),pos[i][0]=i;//f[i][0]为前缀和数组
init();
for(int i=1;i+l-1<=n;++i){
Node a;
a.s=i;
a.l=l+i-1;
a.r=min(r+i-1,n);
a.t=query(a.l,a.r);
a.val=f[a.t][0]-f[a.s-1][0];
heap.push(a);
}
long long ans=0;
for(int i=1;i<=k;i++){
Node a=heap.top();
heap.pop();
ans+=a.val;
Node b=a;//左半区
b.r=a.t-1;
if(b.l<=b.r){
b.t=query(b.l,b.r);
b.val=f[b.t][0]-f[b.s-1][0];
heap.push(b);
}
Node c=a;
c.l=a.t+1;
if(c.l<=c.r){
c.t=query(c.l,c.r);
c.val=f[c.t][0]-f[c.s-1][0];
heap.push(c);
}
}
printf("%lld",ans);
return 0;
} 我的堆是手写的,如果懒,可以用优先队列



京公网安备 11010502036488号