P2048 [NOI2010] 超级钢琴(RMQ 贪心)


传送门

题目描述

在这里插入图片描述

解析

首先,如果只有一个和弦,那么问题显然简单了
前缀和结合ST表随便做做即可
然而
这次要求前k大的
怎么办呢?
参照之前有一道序列合并的做法
我们想到,可以先建一个优先队列,把以每个元素为开头的最大和弦求出来
然后每次弹出队首,再用队首的开头把加进来一些新的
问题是,我受那道题影响,觉得一定要加进来次大的
次大弹出就加第三大的,子子孙孙无穷匮也…
于是我就卡到这里了
----------我的思路和题解的分割线-------------
但是不必拘泥与原来的那个区间
可以把它拆开来
用三元组(st,l,r)表示st开头,终点在[l,r]的最大值和最大值位置
那么取出一个最大值位置在pl的三元组后,可以再弹进去两个:
(st,l,pl-1)(st,pl+1,r)
当然,要注意一些pl在边缘的特判
这样就迎刃而解啦

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=5e5+100;
int n,m,k;
int l,r;
int a[N],sum[N];
struct node{
	int st,l,r,pl,v;
	bool operator < (const node y)const{return v<y.v;}
};
struct node2{
	int v,pl;
};
node2 mx[N][25];
node2 merge(node2 x,node2 y){
	if(x.v>y.v) return x;
	else return y;
}
int mi[N],lg[N];
void solve(){
	mi[0]=1;
	for(int i=1;i<=20;i++) mi[i]=mi[i-1]<<1;
	int k=0;
	for(int i=1;i<=n;i++){
		if(i>=mi[k]) k++;
		lg[i]=k-1;
	}
	for(int i=1;i<=n;i++) mx[i][0]=(node2){sum[i],i};
	for(int k=1;k<=lg[n];k++){
		for(int i=1;i+mi[k]-1<=n;i++){
			mx[i][k]=merge(mx[i][k-1],mx[i+mi[k-1]][k-1]);
		}
	}
	return;
}
node2 ask(int x,int y){
	int k=lg[y-x+1];
	return merge(mx[x][k],mx[y-mi[k]+1][k]);
}
priority_queue<node>q;
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];
	solve();
	for(int i=1;i<=n;i++){
		if(i+l-1>n) break;
		int L=i+l-1,R=min(n,i+r-1);
		node2 x=ask(L,R);
		q.push((node){i,L,R,x.pl,x.v-sum[i-1]});
	}
	ll tot=0;
	for(int i=1;i<=k;i++){
		node o=q.top();q.pop();
		tot+=o.v;
		int st=o.st;
		if(o.pl!=o.l){
			node2 x=ask(o.l,o.pl-1);
			q.push((node){st,o.l,o.pl-1,x.pl,x.v-sum[st-1]});
		}
		if(o.pl!=o.r){
			node2 x=ask(o.pl+1,o.r);
			q.push((node){st,o.pl+1,o.r,x.pl,x.v-sum[st-1]});
		}
	}
	printf("%lld",tot);
	return 0;
}
/*
6
2002 4920
2003 5901
2004 2832
2005 3890
2007 5609
2008 3024
5
2002 2005
2003 2005
2002 2007
2003 2007
2005 2008
*/





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值