洛谷 P2048超级钢琴 超详细题解

updata 2024/5/15 18:32:12:整理了一下文章内容

题目描述

小 Z 是一个小有名气的钢琴家,最近 C 博士送给了小 Z 一架超级钢琴,小 Z 希望能够用这架钢琴创作出世界上最美妙的音乐。

这架超级钢琴可以弹奏出 n n n 个音符,编号为 1 1 1 n n n。第 i i i 个音符的美妙度为 A i A_i Ai,其中 A i A_i Ai 可正可负。

一个“超级和弦”由若干个编号连续的音符组成,包含的音符个数不少于 L L L 且不多于 R R R。我们定义超级和弦的美妙度为其包含的所有音符的美妙度之和。两个超级和弦被认为是相同的,当且仅当这两个超级和弦所包含的音符集合是相同的。

小 Z 决定创作一首由 k k k 个超级和弦组成的乐曲,为了使得乐曲更加动听,小 Z 要求该乐曲由 k k k 个不同的超级和弦组成。我们定义一首乐曲的美妙度为其所包含的所有超级和弦的美妙度之和。小 Z 想知道他能够创作出来的乐曲美妙度最大值是多少。


题目解答

主要思路

这道题相当于找 k k k 段长度在 L ∼ R L \sim R LR 的不同区间,使这 k k k 段区间的总和最大。我们可以枚举左端点,想办法求出右端点应该在那个位置。

假设右端点在 j j j , 左端点在 i i i , 那么这段区间的总和为 s u m [ j ] − s u m [ i − 1 ] sum[j] - sum[i-1] sum[j]sum[i1] , 既然我们枚举左端点,那么 s u m [ i − 1 ] sum[i-1] sum[i1] 就已经固定,我们只需让 s u m [ j ] sum[j] sum[j] 最大即可,相当于找 s u m sum sum i + L − 1 ∼ i + R − 1 i+L-1 \sim i+R-1 i+L1i+R1 中的最大值。

在这里插入图片描述
发现没,这不就是 R M Q RMQ RMQ 吗?


RMQ

RMQ 是英文 Range Maximum/Minimum Query 的缩写,表示区间最大(最小)值。

about RMQ

警钟撅烂

注意, R M Q RMQ RMQ 要存下标,不要漏掉加一减一,一定不要写错!!!如果WA了,一定要来此对照 R M Q RMQ RMQ 模版

void init()
{
	for(int i=1;i<=n;++i)
		rmq[i][0]=i;
	int t=log2(n);
	for(int j=1;j<=t;++j)
		for(int i=1;i+(1<<j)-1<=n;++i)
			rmq[i][j]=sum[rmq[i][j-1]]>sum[rmq[i+(1<<j-1)][j-1]]?rmq[i][j-1]:rmq[i+(1<<j-1)][j-1];
}

int query(int l,int r)
{
	int t=log2(r-l+1);
	return sum[rmq[l][t]]>sum[rmq[r-(1<<t)+1][t]]?rmq[l][t]:rmq[r-(1<<t)+1][t];
}

特殊情况

R M Q RMQ RMQ 预处理后,就是枚举左端点,求出右端点,并找出总和最大的区间。怎么找呢?我们可以用优先队列。将找到的区间扔进优先队列里面。扔的时候总共要带四个值:

  • 区间左端点
  • 区间右端点
  • 区间右端点的下界
  • 区间右端点的上界

第三个值和第四个值为什么要带,后面就知道了。


那么,将区间都找完了,就万事大吉了吗?NO!

我们可以看一组数据:

2 2 1 2
100 -100

如果仅按上述方法做,找到的两个区间应该是 1 ∼ 1 1 \sim 1 11 2 ∼ 2 2 \sim 2 22
发现不对了吗?正确答案应该是 1 ∼ 1 1 \sim 1 11 1 ∼ 2 1 \sim 2 12 才对。

所以,这 k k k 个区间的左端点有可能相同,循环枚举左端点时,我们不能只扔一个右端点,得扔多个。

具体扔多少个,我们没法知道,就只能在后面从优先队列中拿出总和前 k k k 大的区间这个部分下手。

我们拿到一个区间后,可以求出左端点相同的总和次大区间,将这个区间又扔回优先队列里面。

如果我们将前面枚举左端点时扔进优先队列里面的四元组表示为 ( i , j , x , y ) (i,j,x,y) (i,j,x,y) , i i i 表示为区间左端点, j j j 表示为区间右端点, x x x y y y 分别表示为区间右端点下上界,那么,我们可以将这个四元组拆成 ( i , q u e r y ( x , j − 1 ) , x , j − 1 ) (i,query(x,j-1),x,j-1) (i,query(x,j1),x,j1) ( i , q u e r y ( j + 1 , y ) , j + 1 , y ) (i,query(j+1,y),j+1,y) (i,query(j+1,y),j+1,y) 两个四元组,重新扔进优先队列里面。

最后,我们只需要将我们取出的 k k k 个区间总和求和后就可以输出了。


Code

#include<bits/stdc++.h>
#include<queue>
using namespace std;

#define N 500005
#define int long long//开long long!!!

inline int read()
{
	int x=0,f=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
	return x*f;
}

int n,k,L,R;
int sum[N],rmq[N][30];
struct node
{
	int i,j,x,y;//i:区间左端点 j:区间右端点 x:区间右端点下界 y:区间右端点上界
	node(){}
	node(int i,int j,int x,int y):i(i),j(j),x(x),y(y){}
	bool operator<(const node &jt)const{return sum[j]-sum[i-1]<sum[jt.j]-sum[jt.i-1];}
};
priority_queue<node>q;

void init()//RMQ要存下标,不要写错了!!!
{
	for(int i=1;i<=n;++i)
		rmq[i][0]=i;
	int t=log2(n);
	for(int j=1;j<=t;++j)
		for(int i=1;i+(1<<j)-1<=n;++i)
			rmq[i][j]=sum[rmq[i][j-1]]>sum[rmq[i+(1<<j-1)][j-1]]?rmq[i][j-1]:rmq[i+(1<<j-1)][j-1];
}

int query(int l,int r)
{
	int t=log2(r-l+1);
	return sum[rmq[l][t]]>sum[rmq[r-(1<<t)+1][t]]?rmq[l][t]:rmq[r-(1<<t)+1][t];
}//那个加一别漏了!!!

signed main()
{
	n=read(),k=read(),L=read(),R=read();
	for(int i=1;i<=n;++i)
		sum[i]=sum[i-1]+read();
	init();//这个别忘写!!!
	for(int i=1;i+L-1<=n;++i)//枚举左端点,RMQ求右端点,扔进优先队列
		q.push(node(i,query(i+L-1,min(n,i+R-1)),i+L-1,min(n,i+R-1)));
	int ans=0;
	while(k--)
	{
		node temp=q.top();q.pop();
		ans+=sum[temp.j]-sum[temp.i-1];//累加
		if(temp.j<temp.y)//右半部分
			q.push(node(temp.i,query(temp.j+1,temp.y),temp.j+1,temp.y));
		if(temp.j>temp.x)//左半部分
			q.push(node(temp.i,query(temp.x,temp.j-1),temp.x,temp.j-1));
	}
	printf("%lld",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值