P2048 [NOI2010]超级钢琴
前缀和 ST表 优先队列 贪心
贪心
&
\&
&优先队列:每次选最优的子段,选
k
k
k次就是我们所要的结果,用堆将解存进去,每次堆顶的元素就是最优解。
前缀和:如果每次都计算当前子段区间的和显然时间复杂度爆炸,故联想到直接采用前缀和在
O
(
1
)
O(1)
O(1)时间内求出
ST表:存储当前节点的
l
→
r
l\to r
l→r区间内的最大值,注意因为查询的时候需要查询最优解的位置所以此时的ST表内存储的是下标位置而不是直接的值
注意当这段被提取出来了以后,要把提取位置两侧的子段放入优先队列中
t
t
t:以
o
o
o点为开头,位置在
[
l
,
r
]
[l,r]
[l,r]之间的最大值的位置
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int M=(int)5e5+50;
const int L=22;
int ST[L][M],lg[M],t,o,l,r;
ll sum[M];
int query(int l,int r){
int k=log(r-l+1)/log(2);
int x=ST[k][l],y=ST[k][r-(1<<k)+1];
return sum[x]>sum[y]?x:y;
}
struct node{
int o,l,r,t;
node () {}
node(int o,int l,int r):o(o),l(l),r(r),t(query(l,r)){}
friend bool operator<(const node &a,const node &b){
return sum[a.t]-sum[a.o-1]<sum[b.t]-sum[b.o-1];
}
};
priority_queue<node> Q;
inline int read(){
int cnt=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-f;c=getchar();}
while(isdigit(c)){cnt=(cnt<<3)+(cnt<<1)+(c^48);c=getchar();}
return cnt*f;
}
void init(int n){
for(int i=1;i<=n;++i){
ST[0][i]=i;
}
for(int j=1;(1<<j)<=n;++j){
for(int i=1;i+(1<<j)-1<=n;++i){
int x=ST[j-1][i];int y=ST[j-1][i+(1<<j-1)];
ST[j][i]=sum[x]>sum[y]?x:y;
}
}
}
int main(){
int n,k,L,R;
n=read(),k=read(),L=read(),R=read();
for(int i=1;i<=n;++i){
scanf("%lld",&sum[i]);
sum[i]+=sum[i-1];
}
init(n);
for(int i=1;i<=n;++i){
if(i+L-1<=n){
Q.push(node(i,i+L-1,min(i+R-1,n)));
}
}
ll ans=0;
while(k--){
int o=Q.top().o,l=Q.top().l,r=Q.top().r,t=Q.top().t;
Q.pop();
ans+=sum[t]-sum[o-1];
if(l!=t)Q.push(node(o,l,t-1));
if(r!=t)Q.push(node(o,t+1,r));
}
printf("%lld",ans);
return 0;
}