P2048 [NOI2010] 超级钢琴 题解
P2048 [NOI2010] 超级钢琴
Description
给你一个长度为 \(n\) 的序列 \(A\),要求出所有长度在 \(L\) 到 \(R\) 之间的区间中区间和前 \(k\) 大的区间的区间和之和。
Solution
考虑设计一个类似 dp 状态的东西:令三元组 \((o,l,r)\) 代表区间左端点为 \(o\),右端点在 \(l,r\) 区间内的区间和最大值。
我们考虑从 \(1\) 到 \(n\) 枚举每一个 \(o\),再从 \(o+L-1\) 开始枚举。贪心地想,最终答案包含了满足条件的区间的前 \(k\) 大,那就可以用一个优先队列来存三元组,每次得到 最大值 就加到答案里,求 \(k\) 次就是最终的答案。
这个静态区间最大值就可以使用 ST 表预处理。
本题有个坑:
假设当前三元组 \((o,l,r)\) 中区间和最大的区间右端点为 \(p\),在计算完 \((o,p)\) 对答案的贡献后,\(p\) 的左边和右边仍可能产生贡献。所以在维护优先队列时应额外 push 两次。
push(o,l,p-1);
push(o,p+1,r);
复杂度为 \(O(k\log n)\),可以通过。
#include<bits/stdc++.h>
#define int long long
using namespace std;
long long n,k,L,R,A[500005],sum[500005],ST[500005][25],ans;
inline int query(int l,int r){
int k=log2(r-l+1);
int x=ST[l][k],y=ST[r-(1<<k)+1][k];
if(sum[x]>sum[y]){
return x;
}
return y;
}
struct node{
int o,l,r,t;
node(){};
friend bool operator <(const node &a,const node &b){
return sum[a.t]-sum[a.o-1]<sum[b.t]-sum[b.o-1];
}
};
inline node get_t(int o,int l,int r){
node xx;
xx.o=o;
xx.l=l;
xx.r=r;
xx.t=query(l,r);
return xx;
}
priority_queue<node>q;
inline void init(){
for(int i=1;i<=n;i++){
ST[i][0]=i;
}
for(int j=1;(1<<j)<=n;j++){
for(int i=1;i+(1<<j)-1<=n;i++){
int x=ST[i][j-1],y=ST[i+(1<<(j-1))][j-1];
if(sum[x]>sum[y]){
ST[i][j]=x;
}
else{
ST[i][j]=y;
}
}
}
return;
}
signed main(){
cin>>n>>k>>L>>R;
for(int i=1;i<=n;i++){
cin>>sum[i];
sum[i]+=sum[i-1];
}
init();
for(int i=1;i<=n;i++){
if(i+L-1<=n){
q.push(get_t(i,i+L-1,min(i+R-1,n)));
}
}
for(int i=1;i<=k;i++){
int o=q.top().o;
int l=q.top().l;
int r=q.top().r;
int t=q.top().t;
q.pop();
ans+=sum[t]-sum[o-1];
if(l!=t){
q.push(get_t(o,l,t-1));
}
if(t!=r){
q.push(get_t(o,t+1,r));
}
}
cout<<ans<<endl;
return 0;
}

浙公网安备 33010602011771号