bzoj 2006: [NOI2010]超级钢琴
bzoj 2006: [NOI2010]超级钢琴
Time Limit: 20 Sec Memory Limit: 512 MB
Submit: 4491 Solved: 2312
[Submit][Status][Discuss]
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,表示按编号从小到大每个音符的美妙度。
N<=500,000
k<=500,000
-1000<=Ai<=1000,1<=L<=R<=N且保证一定存在满足条件的乐曲
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。
题目大意:
求前k大的长度在L~R之间的最大连续子序列和的和。
题解:
首先先做一个前缀和,设sum[i]表示的是1~i的和
然后枚举右端点 r,区间所对应的左端点只能在 r - R + 1到 r - L + 1 之间,然后对sum数组的r - R 到 r - L 之间取个最小值就可以了,这里直接用st表维护,然后把这个最大的加到大根堆里,维护一个四元组(a, b, c, d),a表示答案,就是以b为右端点,左端点在c~d之间的最大连续子序列和。
然后每次把大根堆堆顶的元素拿出来,把答案加上a然后把这个区间拆成两个区间,假设最大的区间的左端点在mid这个位置,那么这个区间就可以拆成c~mid-1和mid+1~d这两个区间,再把它们加入到堆里,这样做k次就可以了。
code:(好像还比较短)
#include<bits/stdc++.h>
#define int long long
#define N 1000005
#define pr pair<int,int>//毒瘤操作
#define mp(a, b, c, d) make_pair(make_pair(a, b), make_pair(c, d))//毒瘤操作+1
using namespace std;
priority_queue<pair<pr, pr> >q;//毒瘤操作,这样就可以表示四元组了
int sum[N], f[N][24], n, k, L, R;
int mi(int x, int y){//比较x,y这两个位置的sum的值
if(sum[x] < sum[y]) return x;
return y;
}
int query(int x, int y){//求x~y的最小值的位置
if(x > y) return -1;
int k = log2(y - x + 1);
return mi(f[x][k], f[y - (1 << k) + 1][k]);
}
signed main(){
scanf("%lld%lld%lld%lld", &n ,&k, &L, &R);
for(int i = 1; i <= n; i ++) scanf("%lld", &sum[i]), f[i][0] = i, sum[i] += sum[i - 1];
for(int j = 1; j <= 20; j ++)
for(int i = 0; i <= n; i ++)
f[i][j] = mi(f[i][j - 1], (i + (1 << (j - 1)) <= n)? f[i + (1 << (j - 1))][j - 1]:f[i][j - 1]);//st表
for(int i = L; i <= n; i ++) q.push(mp(sum[i] - sum[query(max(i - R, 0ll), i - L)], i, max(i - R, 0ll), i - L));//先把一开始的加进去
int ans = 0;
for(int i = 1; i <= k; i ++){
pr h1 = q.top().first, h2 = q.top().second; q.pop();
int a = h1.first, b = h1.second, c = h2.first, d = h2.second;
ans += a; int mid = query(c, d);
int x = query(c, mid - 1), y = query(mid + 1, d);//x,y分别为拆开后两个区间的最优左端点
if(x != -1) q.push(mp(sum[b] - sum[x], b, c, mid - 1));//拆成两个区间
if(y != -1) q.push(mp(sum[b] - sum[y], b, mid + 1, d));
}
printf("%lld", ans);
return 0;
}//好棒的一道题!