[题解] 超级钢琴 -- 倍增 RMQ 问题
超级钢琴
这里提供了一个倍增求解子区间前 \(k\) 大值的算法。
-
首先求一个前缀和。
-
对于一个固定的左端点 \(i\) ,可以在 \([l,r]\) 的合法区间内找到一个最大的 \(sum[nw]\) 使得 \(sum[nw]-sum[l-1]\) 最大
-
然后我们对其进行分裂操作(分裂后的区间可能也大),用大根堆维护一下
-
用倍增来找最大的 \(sum[nw]\)
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;
const int maxn = 524288 + 1000,INF = 2e9;
int n,k,l,r,sum[maxn],f[maxn][22],id[maxn][22];
struct node{
int id,l,r,nw;//起点,左端点,右端点,取到最大值的位置
bool operator <(node a)const{
return sum[nw]-sum[id-1]<sum[a.nw]-sum[a.id-1];
}
};
long long ans = 0;
priority_queue<node> q;
void init(){
for(int j=1;j<=19;j++){
for(int i=1;i<=n;i++){
if(i+(1<<j-1)<=n){
if(f[i][j-1]>f[i+(1<<j-1)][j-1]){
f[i][j]=f[i][j-1];
id[i][j]=id[i][j-1];
}
else{
f[i][j]=f[i+(1<<j-1)][j-1];
id[i][j]=id[i+(1<<j-1)][j-1];
}
}
}
}
}
int query(int L,int R){
int Max=-2e9,ID=-1;
for(int j=19;j>=0;j--){
if(L+(1<<j)-1<=R){
if(Max<f[L][j]){
Max=f[L][j];
ID=id[L][j];
}
L=(L+(1<<j));
}
}
return ID;
}
int main(){
freopen("1.in","r",stdin);
scanf("%d%d%d%d",&n,&k,&l,&r);
for(int i=1;i<=n;i++){
int tmp;scanf("%d",&tmp);
sum[i]=sum[i-1]+tmp;
f[i][0]=sum[i];id[i][0]=i;
}
init();
for(int i=1;i<=n-l+1;i++){
node tmp;
tmp.nw=query(i+l-1,min(i+r-1,n));
tmp.l=i+l-1;tmp.r=min(i+r-1,n);
tmp.id=i;
q.push(tmp);
}
while(k--){
node t=q.top();q.pop();
ans+=(long long)sum[t.nw]-sum[t.id-1];
if(t.nw>t.l){
node tmp;
tmp.l=t.l;tmp.r=t.nw-1;
tmp.nw=query(tmp.l,tmp.r);
tmp.id=t.id;
q.push(tmp);
}
if(t.nw<t.r){
node tmp;
tmp.l=t.nw+1;tmp.r=t.r;
tmp.nw=query(tmp.l,tmp.r);
tmp.id=t.id;
q.push(tmp);
}
}
printf("%lld",ans);
return 0;
}