BZOJ 2006: [NOI2010]超级钢琴

题目大意:

一个区间的价值为区间内所有数的和

求序列中长度在L至R的区间中价值前k大的区间的价值和。

题解:

一个区间价值用s[r]-s[l]来表示,s为前缀和。假设固定了右端点,则可以通过st表来确定左端点的最优位置。对于所有的右端点,我们将他们扔进堆里就能找出最大值来。

然后对于一个右端点确定了一个左端点,之后对于这个右端点这个左端点就不能选了,所以把原来区间断成两端,再扔进堆里。重复k次就可以了。

代码:

#include<cstdio>
#include<algorithm>
#include<queue>
#define ma make_pair
#define pr pair<int,int>
#define prr pair<pr,pr>
#define mp make_pair
#define fr first
#define sc second
using namespace std;
int n,k,l,r,a[1000005],lg[1000005],st[500005][21],sum[1000005];
priority_queue<prr> q;
int calc(int a,int b){
	if (sum[a]<sum[b]) return a;
	else return b;
}
int query(int a,int b){
	if (a>b) return -1;
	int len=lg[b-a+1];
	return calc(st[a][len],st[b-(1<<len)+1][len]);
}
int main(){
	scanf("%d%d%d%d",&n,&k,&l,&r);
	for (int i=1; i<=n; i++){
		scanf("%d",&a[i]);
		sum[i]=sum[i-1]+a[i]; 
	}
	for (int i=2; i<=n; i++) lg[i]=lg[i>>1]+1;
	for (int i=1; i<=n; i++) st[i][0]=i;
	for (int j=1; (1<<j)<=n; j++)
		for (int i=0; i+(1<<j)-1<=n; i++)
			st[i][j]=calc(st[i][j-1],st[i+(1<<j-1)][j-1]);
	for (int i=l; i<=n; i++)
		q.push(mp(mp(sum[i]-sum[query(max(i-r,0),i-l)],i),mp(max(i-r,0),i-l)));
	long long ans=0;
	for (int i=1; i<=k; i++){
		ans+=q.top().fr.fr;
		int x=q.top().fr.sc,a=q.top().sc.fr,b=q.top().sc.sc,y=query(a,b);
		q.pop();
		int id1=query(a,y-1),id2=query(y+1,b);
		if (id1!=-1) q.push(mp(mp(sum[x]-sum[id1],x),mp(a,y-1)));
		if (id2!=-1) q.push(mp(mp(sum[x]-sum[id2],x),mp(y+1,b)));
	} 
	printf("%lld
",ans);
	return 0;
}

  

原文地址:https://www.cnblogs.com/silenty/p/8733051.html