bzoj2006 超级钢琴

传送门

参考博客

区间长度是动的,考虑定一个右端点,那么左端点可以动。

对于每一个右端点,通过左端点移动,在给定长度范围[L,R]间,可以对应出一个最优解。

那么次优解,次次优解...怎么办呢。。

我们可以把左端点对应的范围[a,b]给分解成[a,pos-1],[pos+1,b]。其中pos为当前右端点对应最优解的位置。

这时候右端点还是刚才的右端点,而左端点的区间分成两半。那么次优解就在这两半中。

 

具体实现如下:

用ST表求前缀和最小值。那么对于一个位置i来说,它左端点的范围是[i-R+1,i-L+1]。

如果位置i的前缀和是sum[i],那么它对应的最优解就是(sum[i]-[i-R,i-L]中的前缀和最小值)

分解怎么办呢?

用一个优先队列,里面的一个元素记录四条信息:最优解的大小,左端点的左边界,左端点的右边界,右端点的位置。

这样我们就可以把元素分裂成两个丢进去了。

 

最大值一定在第一波里面(就是还没有元素还没有分裂的时候)。因为只有长度限制,没有端点不能取的限制。

第一波元素进队列后,最优解是从大到小排的。那么最大值一定是top。

次大值一定对应(top元素分出来的两个之一)或者(第二个元素,也就是当前次大元素)。

次大值不可能在top元素的后面的元素分裂出来的元素中,因为每个元素装的是最优解,并且最优解是从大到小排的!

如果后面的元素分裂后得出来的最优解比当前top元素对应的最优解都还要大,说明后面的这个元素分裂前对应的那个最优解都已经比top元素大了,这与从大到小排序矛盾。

那么我们把当前的top元素取出来,分裂,丢进去,下次再进来取,就一定取的是当前最大值,以此类推。。

 

几个注意事项:

输入有负数,读优带上负号,ST表存的是位置,要开long long。注意<符号的重载

注意区间范围,还有端点加一减一啥的。写的时候要想好各个参数的具体含义!

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=5e5+10;
const int mx=20;
int st[maxn][mx];
int n,k,L,R;
ll presum[maxn],ans=0;
struct elements{
	ll sum;
	int lpos,rpos,R;
	elements(){}
	elements(int a,int b,int c,int d):sum(a),lpos(b),rpos(c),R(d){}
	friend inline bool operator<(const elements &a,const elements &b){return a.sum<b.sum;}
}a[maxn];
priority_queue<elements> Q;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1; ch=getchar();}
	while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
	return x*f;
}
inline int MIN(int a,int b){return (presum[a]<presum[b])?(a):(b);}
inline int query(int l,int r){
	if(l>r) return -1;
	int k=log2(r-l+1);
	return MIN(st[r-(1<<k)+1][k],st[l][k]);
}
int main(){
	n=read(),k=read(),L=read(),R=read();
	for(int i=1;i<=n;++i) presum[i]=presum[i-1]+read(),st[i][0]=i;
	for(int i=1;(1<<i)<=n;++i)
		for(int j=0;((1<<i)+j-1)<=n;++j)
			st[j][i]=MIN(st[j][i-1],st[j+(1<<(i-1))][i-1]);

	for(int i=L;i<=n;++i){
		int now_pos=query(max(0,i-R),i-L);
		Q.push(elements(presum[i]-presum[now_pos],max(i-R,0),i-L,i));
	}
	for(int i=1;i<=k;++i){
		elements N=Q.top();
		Q.pop(),ans+=N.sum;
		int now_pos=query(N.lpos,N.rpos);
		int d1=query(N.lpos,now_pos-1);
		int d2=query(now_pos+1,N.rpos);
		if(d1!=-1) Q.push(elements(presum[N.R]-presum[d1],N.lpos,now_pos-1,N.R));
		if(d2!=-1) Q.push(elements(presum[N.R]-presum[d2],now_pos+1,N.rpos,N.R));
	}
	cout<<ans;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值