【bzoj2006】[NOI2010]超级钢琴 堆+st表

3 篇文章 0 订阅
2 篇文章 0 订阅

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


思路好棒…

定义(i,l,r,t)为当前左端点为i,右端点可选区间为[l,r],当前最优右端点为t,则当前区间的权值是 sum[t]sum[i1]

然后维护一个堆,里面存当前的前k大区间,按权值排序。每次取出最大的,可分裂成(i,l,t-1,t1)和(i,t+1,r,t2),然后继续就行了,一共取出k次,取出一个就算一下答案。这样每次取出的都是最大的。

关于如何算t,在区间[l,r]中选出一个t使得 sum[t]sum[i1] 最大。我们发现sum[i-1]是固定的,所以使sum[t]最大就行了,这是RMQ问题,st表可以O(1)。

因为每次取出一个放进去俩,所以最后堆里有n+k个元素,复杂度是 O(klog(n+k))

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<iostream>
#include<cmath>
using namespace std;

typedef long long LL;
const int SZ = 1000010; 

int n,k,L,R;
LL num[SZ];

struct qj{
    int i,l,r,t;
};

bool operator <(qj a,qj b)
{
    return num[a.t] - num[a.i - 1] < num[b.t] - num[b.i - 1];
}

priority_queue<qj> q;


int mx[SZ][30]; 

void get_st()
{
    for(int i = 1;i <= n;i ++) mx[i][0] = i;
    for(int j = 1;j <= log2(n);j ++)
        for(int i = 1;i <= n;i ++)
        {
            int x = mx[i][j - 1];
            int y = mx[i + (1 << (j - 1))][j - 1];
            mx[i][j] = num[x] < num[y] ? y : x;
        }
}

int ask(int l,int r)
{
    int k = log2(r - l + 1);
    int x = mx[l][k],y = mx[r - (1 << k) + 1][k];
    return num[x] < num[y] ? y : x;
}

int main()
{
    scanf("%d%d%d%d",&n,&k,&L,&R);
    for(int i = 1;i <= n;i ++)
        scanf("%lld",&num[i]);
    for(int i = 1;i <= n;i ++)
        num[i] += num[i - 1];
    get_st();
    for(int i = 1;i <= n;i ++)
    {
        if(i + L - 1 <= n)
        {
            int l = i + L - 1;
            int r = min(i + R - 1,n);
            q.push((qj){i,l,r,ask(l,r)});
        }
    } 
    LL ans = 0;
    while(k --)
    {
        qj x = q.top(); q.pop();
        ans += num[x.t] - num[x.i - 1];
        if(x.t != x.l) q.push((qj){x.i,x.l,x.t - 1,ask(x.l,x.t - 1)});
        if(x.t != x.r) q.push((qj){x.i,x.t + 1,x.r,ask(x.t + 1,x.r)});
    }
    printf("%lld\n",ans);
    return 0; 
}
/*
1 1 1 1
*/

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值