BZOJ 2006 [NOI2010]超级钢琴

 题解:

枚举左端点,找到合法的最大的右端点

依次选取m段

当某一段的最大右端点被选过了,才能选次大右端点

开一个堆,维护元素(begin,maxp,l,r)表示当前begin为左端点,l,r为合法区间,maxp为最大右端点

每次选取贡献最大的区间,然后按maxp分裂成两部分,次大右端点在这两部分中,放入堆里

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
using namespace std;
const int maxn=500009;
typedef long long Lint;

int n,m,lo,hi;
Lint ans;
int s[maxn];
struct Sec{
	int b,p,l,r;
	bool operator < (const Sec &rhs) const{
		return s[p]-s[b-1]<s[rhs.p]-s[rhs.b-1];
	}
	Sec(int bb,int pp,int ll,int rr){
		b=bb;p=pp;l=ll;r=rr;
	}
};
priority_queue<Sec>q;

int f[maxn][20];
int g[maxn][20];
void STinit(){
	for(int i=1;i<=n;++i){
		f[i][0]=s[i];
		g[i][0]=i;
	}
	for(int j=1;j<=19;++j){
		for(int i=1;i+(1<<j)-1<=n;++i){
			if(f[i][j-1]>f[i+(1<<(j-1))][j-1]){
				f[i][j]=f[i][j-1];
				g[i][j]=g[i][j-1];
			}else{
				f[i][j]=f[i+(1<<(j-1))][j-1];
				g[i][j]=g[i+(1<<(j-1))][j-1];
			}
		}
	}
}

int Querymax(int l,int r){
	int k=log2(r-l+1.5);
	if(f[l][k]>f[r-(1<<k)+1][k]){
		return g[l][k];
	}else{
		return g[r-(1<<k)+1][k];
	}
}

int main(){
	scanf("%d%d%d%d",&n,&m,&lo,&hi);
	for(int i=1;i<=n;++i){
		scanf("%d",&s[i]);
		s[i]+=s[i-1];
	}
	
	STinit();
	for(int i=1;i<=n;++i){
		int tl=i+lo-1;
		int tr=min(i+hi-1,n);
		if(tl>tr)continue;
		int p=Querymax(tl,tr);
		q.push(Sec(i,p,tl,tr));
	}
	
	while(m--){
		Sec t=q.top();q.pop();
		int b=t.b;
		int p=t.p;
		int tl=t.l;
		int tr=t.r;
		int p1;
		ans+=(s[p]-s[b-1]);
		if(p!=tl){
			p1=Querymax(tl,p-1);
			q.push(Sec(b,p1,tl,p-1));
		}
		if(p!=tr){
			p1=Querymax(p+1,tr);
			q.push(Sec(b,p1,p+1,tr));
		}
	}
	
	cout<<ans<<endl;
	return 0;
}

  

posted @ 2018-02-28 21:13  ws_zzy  阅读(128)  评论(0编辑  收藏  举报