【BZOJ 2006】 [NOI2010]超级钢琴

2006: [NOI2010]超级钢琴

Time Limit: 20 Sec   Memory Limit: 552 MB
Submit: 1535   Solved: 737
[ Submit][ Status]

Description

小Z是一个小有名气的钢琴家,最近C博士送给了小Z一架超级钢琴,小Z希望能够用这架钢琴创作出世界上最美妙的音乐。 这架超级钢琴可以弹奏出n个音符,编号为1至n。第i个音符的美妙度为Ai,其中Ai可正可负。 一个“超级和弦”由若干个编号连续的音符组成,包含的音符个数不少于L且不多于R。我们定义超级和弦的美妙度为其包含的所有音符的美妙度之和。两个超级和弦被认为是相同的,当且仅当这两个超级和弦所包含的音符集合是相同的。 小Z决定创作一首由k个超级和弦组成的乐曲,为了使得乐曲更加动听,小Z要求该乐曲由k个不同的超级和弦组成。我们定义一首乐曲的美妙度为其所包含的所有超级和弦的美妙度之和。小Z想知道他能够创作出来的乐曲美妙度最大值是多少。

Input

第一行包含四个正整数n, k, L, R。其中n为音符的个数,k为乐曲所包含的超级和弦个数,L和R分别是超级和弦所包含音符个数的下限和上限。 接下来n行,每行包含一个整数Ai,表示按编号从小到大每个音符的美妙度。

Output

只有一个整数,表示乐曲美妙度的最大值。

Sample Input

4 3 2 3

3

2

-6

8

Sample Output

11

【样例说明】
共有5种不同的超级和弦:

音符1 ~ 2,美妙度为3 + 2 = 5
音符2 ~ 3,美妙度为2 + (-6) = -4
音符3 ~ 4,美妙度为(-6) + 8 = 2
音符1 ~ 3,美妙度为3 + 2 + (-6) = -1
音符2 ~ 4,美妙度为2 + (-6) + 8 = 4
最优方案为:乐曲由和弦1,和弦3,和弦5组成,美妙度为5 + 2 + 4 = 11。

HINT

Source

Day1



堆+RMQ


定义一个四元组(i,l,r,t)表示是以第i个为开头的,结尾在l-r之间(满足长度>=L,<=R),且结尾在t的权值和最大。


首先把以每一位开头的四元组加入堆,此时的l,r是恰好满足长度>=L,<=R;


堆中排序的关键字是权值之和。


然后把堆顶取出(i,l,r,t),然后再把(i,l,t-1,t')和(i,t+1,r,t'')加入堆中(因为要满足任意两个序列不同的条件)


那么四元组中的t如何快速求出?


用RMQ即可:维护前缀和,权值和就是sum[t]-sum[i-1],对于用一个四元组sum[i-1]相同,因此只要找到l-r中sum[i]最大的即可。


#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <queue>
#define mp(a,b,c,d) (data){a,b,c,d}
#define inf 100000000
#define LL long long
#define M 500005
struct data
{
	int i,l,r,t;
};
using namespace std;
int a[M],Log[M],f[M][20],n,k,l,r;
LL ans=0LL;
void RMQ()
{
	for (int i=1;i<=n;i++)
		f[i][0]=i;
	for (int j=1;(1<<j)<=n;j++)
		for (int i=1;i+(1<<j)-1<=n;i++)
		{
			int x1=f[i][j-1],x2=f[i+(1<<(j-1))][j-1];
			f[i][j]=a[x1]>a[x2]?x1:x2;
		}
	Log[0]=-1;
	for (int i=1;i<=n;i++)
		Log[i]=Log[i>>1]+1;
}
inline int Query(int l,int r)
{
	if (l==r) return l;
	int t=Log[r-l+1];
	int x1=f[l][t],x2=f[r-(1<<t)+1][t];
	return a[x1]>a[x2]?x1:x2;
}
inline bool operator <(data x,data y)
{
	return a[x.t]-a[x.i-1]<a[y.t]-a[y.i-1];
}
void Solve()
{
	priority_queue<data,vector<data> > q;
	for (int i=1;i<=n;i++)
		if (i+l-1<=n)
		{
			int x=min(n,i+r-1);
			q.push(mp(i,i+l-1,x,Query(i+l-1,x)));
		}
	for (int i=1;i<=k;i++)
	{
		data x=q.top();
		q.pop();
		ans+=(a[x.t]-a[x.i-1]);
		if (x.t-1>=x.l) q.push(mp(x.i,x.l,x.t-1,Query(x.l,x.t-1)));
		if (x.t+1<=x.r) q.push(mp(x.i,x.t+1,x.r,Query(x.t+1,x.r)));
	}
}
int main()
{
    scanf("%d%d%d%d",&n,&k,&l,&r);
	a[0]=0;
	for (int i=1;i<=n;i++)
		scanf("%d",&a[i]),a[i]+=a[i-1];
	RMQ();
	Solve();
	printf("%lld\n",ans);
	return 0;
}



感悟:

1.RE是因为>>写成<<


2.感觉这道题和wc第一题有点相似。。非常巧妙~堆中每次删除一个加入两个,保证堆中元素是O(n)的

你可能感兴趣的:(堆,RMQ,OI,bzoj)