洛谷P2048 [NOI2010] 超级钢琴

该博客介绍了洛谷P2048题目,即寻找长度在[L,R]区间内和前k大子区间的和。通过将区间和转化为前缀和之差,利用ST表解决静态区间最大值问题,并通过堆实现前k大子区间和的求解,复杂度为O(klogN)。" 133284077,20036774,编程中的脆弱性、鲁棒性和反脆弱性解析,"['编程', '软件安全', '异常处理', '算法优化']

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题面大意:给定长为 n n n的序列,求长度在 [ L , R ] [L,R] [L,R]区间和前 k k k大子区间之和

首先区间和我们可以转换成前缀和之差
s u m [ l , r ] = s [ r ] − s [ l − 1 ] sum[l,r]=s[r]-s[l-1] sum[l,r]=s[r]s[l1]

暴力的做法是先枚举左端点 l l l,然后暴力枚举长度合法的区间对应的右端点 r r r,找出这些区间和,然后取出前 k k k

显然过不了是吧……

别急,我们再来看一下刚才这个暴力的思路

a n s = ∑ ( s [ r ] − s [ l − 1 ] ) ( 1 ≤ l ≤ r ≤ n ) ans=\sum(s[r]-s[l-1])(1\leq l \leq r \leq n) ans=(s[r]s[l1])(1lrn)

固定 l l l不动,那么我们只需要求 s [ r ] s[r] s[r]最大值——也就是静态区间最大

很明显的RMQ了,那么我们就考虑用ST表维护

但是问题是,我们如果用ST去单纯地处理最大值,那么我们只能处理 k = 1 k=1 k=1的情况——我们可以求出 s u m sum sum最大的那一个区间——但是我们需要前 k k k

是不是有点儿静态区间前 k k k大那味儿了?

于是会有人想要优雅地来写一发主席树

由于博主此时还没有复习到主席树,这里先讲一下堆的做法,以后再把主席树做法补上……

设一个元素为 e l e ( l , r , p o s , b e g ) ele(l,r,pos,beg) ele(l,r,pos,beg) b e g beg beg是枚举的左端点, l l l r r r是对应的枚举的右端点, p o s pos pos是取到最大值对应的右端点,这个 p o s pos pos我们用ST表维护

b e g = 1   n beg=1~n beg=1 n的元素全部放入堆中,取出区间和最大的那个元素 e l e 0 ( l , r , p o s , b e g ) ele0(l,r,pos,beg) ele0(l,r,pos,beg),加入答案后把 e l e 0 ele0 ele0“拆”成 e l e 1 ( l , p o s − 1 , p o s 1 , b e g ) ele1(l,pos-1,pos1,beg) ele1(l,pos1,pos1,beg) e l e 2 ( p o s + 1 , r , p o s 2 , b e g ) ele2(pos+1,r,pos2,beg) ele2(pos+1,r,pos2,beg)两个元素,其中 p o s 1 pos1 pos1 p o s 2 pos2 pos2用ST表维护

如法炮制,我们能把前 k k k大的元素全部取出

由于每次加 2 2 2个元素只加了 k − 1 k-1 k1次,复杂度可以认为是 O ( k l o g N ) O(klogN) O(klogN)

#include<bits/stdc++.h>
using namespace std;
inline char nc(){
    static char buf[100000],*p1=buf,*p2=buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline int rd(){
	int register data=0,w=1;static char ch=0;ch=nc();
	while(!isdigit(ch)&&ch!='-')ch=nc();
	if(ch=='-')w=-1,ch=nc();
	while(isdigit(ch))data=(data<<1)+(data<<3)+(ch^48),ch=nc();
	return data*w;
}
const int N=5e5+1;
int n,k,L,R,p[N][20],lg[N],s[N];
long long ans;
inline int query(int a,int b){
	int register len=lg[b-a+1],x=p[a][len],y=p[b-(1<<len)+1][len];
	return s[x]>s[y]?x:y;
}
struct ele{
	int l,r,pos,beg;
	ele(){}
	ele(int l,int r,int beg):l(l),r(r),pos(query(l,r)),beg(beg){}
	inline friend bool operator<(const ele&a,const ele&b){return s[a.pos]-s[a.beg-1]<s[b.pos]-s[b.beg-1];}
};
priority_queue<ele>q;
signed main(){
	n=rd(),k=rd(),L=rd(),R=rd();
	for(int register i=1;i<=n;++i)s[i]=s[i-1]+rd(),p[i][0]=i;
	for(int register i=2;i<=n;++i)lg[i]=lg[i>>1]+1;
	for(int register j=1;j<=lg[n];++j)
		for(int register a,b,i=1;i+(1<<j)-1<=n;++i)
			a=p[i][j-1],b=p[i+(1<<j-1)][j-1],p[i][j]=s[a]>s[b]?a:b;
	for(int register i=1;i+L-1<=n;++i)q.push(ele(i+L-1,min(n,i+R-1),i));
	while(k--){
		ele register x=q.top();q.pop();
		int register l=x.l,r=x.r,pos=x.pos,beg=x.beg;
		ans+=s[pos]-s[beg-1];
		if(l!=pos)q.push(ele(l,pos-1,beg));
		if(pos!=r)q.push(ele(pos+1,r,beg));
	}cout<<ans,exit(0);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值