ST 表 + 优先队列 + 前缀和
题意
给一个长度 为 n 的序列,让你从中选 k 个长度在 [L,R] 范围内的区间 (同一个区间不可选多次)
要求:这 k 个区间的区间和 相加 得到的值 应该最大
思路
(1):求出前缀和
(2):枚举 i 令其作为区间左边界,则右边界ri可取值 [i+L-1,i+R-1]
使用RMQ在区间[i+L-1,i+R-1]里找到pos => 目标区间里最大值的下标,即:对于左端点 i 有最大区间和 sum[pos] - sum[i-1]
同时,我们把 {i:此时的左边界,i+L-1:右边界最小值,i+R-1:右边界最大值,最大区间和} 作为一个结构体放到优先队列里面
重复执行上述过程,直到 i 枚举结束
(3) 弹出最大值
优先队列 每次会弹出一个 最大区间和 最大的结构体,该结构里含的最大区间和 对答案有贡献
假设此时弹出的结构体为{id,l,r,sum[pos]-sum[id-1]}
即:对于左端点id 其右端点取 pos 时,对答案有贡献
我们不立刻把它舍弃,因为对于左端点 id,其他合法的右端点对答案可能也会有贡献
所以我们在弹出它后,还要把{id,l,pos-1,maxm1}、{id,pos+1,r,maxm2} 给入队,来更新下一个最大值
综上:Code 如下
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5+10;
typedef long long ll;
int lg[N];
int sum[N];
int f[N][20];
int n,k,L,R;
struct node{
int id;
int l,r;
int maxm;
bool operator < (const node & x) const{
return maxm < x.maxm;
}
};
void ST(){
lg[1]=0;
lg[2]=1;
for(int i=3;i<=n;i++) lg[i]=lg[i/2]+1;
int len=lg[n];
for(int j=1;j<=len;j++) for(int i=1;i-1+(1<<j)<=n;i++) {
int x=f[i][j-1],y=f[i+(1<<(j-1))][j-1];
f[i][j]=sum[x]>sum[y]?x:y;
}
}
int query(int l,int r){
int len=lg[r-l+1];
int x=f[l][len],y=f[r+1-(1<<len)][len];
return sum[x]>sum[y]?x:y;
}
int main(){
cin>>n>>k>>L>>R;
for(int i=1;i<=n;i++) cin>>sum[i],sum[i]+=sum[i-1],f[i][0]=i;
ST();
priority_queue<node>q;
for(int i=1;i<=n;i++) {
if(i+L-1>n) break;
if(i+L-1==n) q.push({i,n,n,sum[n]-sum[i-1]});
else if(n>i+L-1) {
int x=query(i+L-1,min(n,i+R-1));
q.push({i,i+L-1,min(n,i+R-1),sum[x]-sum[i-1]});
}
}
ll ans=0;
while(k--){
auto t=q.top();
q.pop();
int id=t.id,l=t.l,r=t.r,maxm=t.maxm;
ans+=maxm;
int index=query(l,r);
if(index-1>=l){
int x=query(l,index-1);
q.push({id,l,index-1,sum[x]-sum[id-1]});
}
if(index+1<=r){
int x=query(index+1,r);
q.push({id,index+1,r,sum[x]-sum[id-1]});
}
}
cout<<ans<<endl;
return 0;
}