P2048 [NOI2010] 超级钢琴(纪念紫题)

原题

题面

在这里插入图片描述

具体实现讲解

首先想到用 s u m sum sum记录 a a a数组的前缀和,把每种和弦都试一遍,很明显会超时。
定义 c a l ( s , l , r ) cal(s,l,r) cal(s,l,r)代表以 s s s为左端点,右端点在 l l l r r r的范围内,能得到的最大和弦美妙度。对于一个右端点 p p p,美妙度为 s u m p − s u m s − 1 sum_p-sum_{s-1} sumpsums1 s u m s − 1 sum_{s-1} sums1确定,只要求 s u m p sum_p sump的最大值,于是用 S T ST ST表维护 s u m sum sum R M Q RMQ RMQ(最大值)。
用优先队列维护 c a l ( s , l , r ) cal(s,l,r) cal(s,l,r)的最大值,只需要取出 k k k个,就可以得到答案。初始时放入所有 c a l ( i , i + L − 1 , i + R − 1 ) cal(i,i+L-1,i+R-1) cal(i,i+L1,i+R1),由于可能出现两个被选中的和弦位于同一个起点,在取出 c a l ( s , l , r ) cal(s,l,r) cal(s,l,r)后要放入 c a l ( s , l , p s − 1 ) cal(s,l,ps-1) cal(s,l,ps1) c a l ( s , p s + 1 , r ) cal(s,ps+1,r) cal(s,ps+1,r) p s ps ps为取到最大的 s u m sum sum的位置)。
S T ST ST表不仅要维护最大值,还要维护最大值的位置; c a l cal cal同样需要返回最优解位置,可以将返回类型定为pair<int,int>;优先队列中的节点要用结构体存储( s s s l l l r r r p s ps ps v a l val val),重载运算符 < < < 即可。

代码

#include<bits/stdc++.h>
using namespace std;
#define maxlog 19
int n,k,L,R,a[500005],sum[500005],Log[500005],cnt;
pair<int,int> ST[500005][20];
long long ans;
struct node{
	int s,l,r,val,ps;
	bool operator < (const node &x) const{ return val<x.val; }
	//priority_queue默认为大根堆,所以return val<x.val
} t;
priority_queue<node,vector<node> > q;
pair<int,int> cal(int s,int l,int r){
	int k=Log[r-l+1];
	if(ST[l][k]>ST[r-(1<<k)+1][k])
		return make_pair(ST[l][k].first-sum[s-1],ST[l][k].second);
	else return make_pair(ST[r-(1<<k)+1][k].first-sum[s-1],ST[r-(1<<k)+1][k].second);
}
int main(){
	scanf("%d%d%d%d",&n,&k,&L,&R);
	Log[1]=0;
	for(int i=2;i<=n;i++) Log[i]=Log[i/2]+1;
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i];
		ST[i][0].first=sum[i];ST[i][0].second=i;
	}
	for(int j=1;j<=maxlog;j++)
		for(int i=1;i+(1<<j)-1<=n;i++){
			if(ST[i][j-1].first>ST[i+(1<<j-1)][j-1].first) ST[i][j]=ST[i][j-1];
			else ST[i][j]=ST[i+(1<<(j-1))][j-1];
		}
	for(int i=1;i+L-1<=n;i++){
		t.s=i,t.l=i+L-1,t.r=min(i+R-1,n);
		pair<int,int> p=cal(t.s,t.l,t.r);
		t.val=p.first,t.ps=p.second;
		q.push(t);
	}
	while(!q.empty()&&cnt<k){
		t=q.top();q.pop();
//		cout<<t.s<<' '<<t.l<<' '<<t.r<<' '<<t.val<<' '<<t.ps<<endl;
		ans+=t.val;cnt++;
		int p=t.ps,tr=t.r,tl=t.l;
		if(p>tl){
			t.r=p-1;
			pair<int,int> pp=cal(t.s,t.l,t.r);
			t.val=pp.first,t.ps=pp.second;
			q.push(t);
		}
		if(p<tr){
			t.l=p+1;t.r=tr;
			pair<int,int> pp=cal(t.s,t.l,t.r);
			t.val=pp.first,t.ps=pp.second;
			q.push(t);
		}
	}
	printf("%lld\n",ans);
	return 0;
}

此文主要用于纪念,如果讲解不够详细,可以转至洛谷题解,大佬很多。(我也是看了题解才做出来的)

  • 25
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值