[题解] 超级钢琴 -- 倍增 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;
}
posted @ 2021-08-12 17:00  ¶凉笙  阅读(34)  评论(0编辑  收藏  举报