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;
}
posted @ 2025-07-18 20:57  Creativexz  阅读(28)  评论(0)    收藏  举报