BZOJ2006 [NOI2010]超级钢琴

58 篇文章 0 订阅

题目描述

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

输入输出格式

输入格式

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

输出格式

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

输入输出样例

输入样例

  • 4 3 2 3
    3
    2
    -6
    8

输出样例

  • 11

说明

  • 共有5种不同的超级和弦:
    1. 音符 1 ~ 2,美妙度为 3+2=5
    2. 音符 2 ~ 3,美妙度为 2+(6)=4
    3. 音符 3 ~ 4,美妙度为 (6)+8=2
    4. 音符 1 ~ 3,美妙度为 3+2+(6)=1
    5. 音符 2 ~ 4,美妙度为 2+(6)+8=4
  • 最优方案为:乐曲由和弦 1 ,和弦3,和弦 5 组成,美妙度为5+2+4=11

原题地址

分析 前缀和 + 堆 + RMQ

  • 把问题转化:求解区间和前 k 大的区间总和
  • 那么根据前缀和把区间和转化为sum[r]sum[l](0l<rn)的形式
  • 考虑到 k 不是非常大,我们一开始对每一个位置结尾的最优区间维护一个堆,那么我们所要完成的唯一操作就是:每次从堆中取出一个元素,在将取出元素对应右端点的次优解区间加入堆中,按照这样连续取k
  • 那么问题的关键就在于如何以较快的速度取这个次优解,考虑对堆中的每一个元素维护四个值 (l,r,x,y)
    • l,r 表示堆中该元素所能取到的最优区间的左右端点(右端点一直固定)
    • x,y 表示堆中该元素所能取到的区间左端点的左右边界
  • 这听起来似乎有点绕,我们举个具体实现的例子
    • 假设我们这次取出一个元素 (l,r,x,y)
    • 那么我们为了防止这次的最优解 l 不再被取到,次优解的位置显然只能在区间[x,l1] [l+1,y] 之间产生
    • 于是我们记区间 [x,l1] 的最优解为 l1 ,区间 [l+1,y] 的最优解为 l2 ,那么我们就可以将取出的元素 (l,r,x,y) 一分为二,分别为 (l1,r,x,l1) (l2,r,l+1,y) ,再加入堆中
  • 最后我们只要用 RMQ 实现取区间最优解的操作就能以 O(nlogn) 的复杂度完成这道题

代码

#include <iostream>
#include <cstdio>

using namespace std;

const int S = 1 << 20;
char frd[S], *hed = frd + S;
const char *tal = hed;

inline char nxtChar()
{
    if (hed == tal) 
     fread(frd, 1, S, stdin), hed = frd;
    return *hed++;
}

inline int get()
{
    char ch; int res = 0; bool flag = false;
    while (!isdigit(ch = nxtChar()) && ch != '-');
    (ch == '-' ? flag = true : res = ch - 48);
    while (isdigit(ch = nxtChar()))
     res = res * 10 + ch - 48;
    return flag ? -res : res;
}

typedef long long ll;
const int N = 5e5 + 5;
ll sum[N], Ans;
int n, K, L, R, Log[N], f[N][20];

struct point 
{
    int l, r, x, y;

    point() {}
    point(int L, int R, int X, int Y):
        l(L), r(R), x(X), y(Y) {}

    friend bool operator > (const point &a, const point &b)
    {
        return sum[a.r] - sum[a.l] > sum[b.r] - sum[b.l];
    }
};

struct Heap
{
    point g[N * 2]; int len;

    inline void Push(const point &res)
    {
        int now = ++len, nxt = len >> 1;
        while (nxt)
        {
            if (res > g[nxt])
             g[now] = g[nxt], nxt = (now = nxt) >> 1;
            else break; 
        }
        g[now] = res;
    }

    inline void Pop()
    {
        int now = 1, nxt = 2; point res = g[len--];
        while (nxt <= len)
        {
            if (nxt < len && g[nxt | 1] > g[nxt]) nxt |= 1;
            if (g[nxt] > res)
             g[now] = g[nxt], nxt = (now = nxt) << 1;
            else break;
        }
        g[now] = res;
    }
}Q;

inline int Min_RMQ(int a, int b) {return sum[a] < sum[b] ? a : b;}
inline int Max(int a, int b) {return a > b ? a : b;}

inline void Init_RMQ()
{
    Log[0] = -1;
    for (int i = 1; i <= n; ++i) 
     f[i][0] = i, Log[i] = Log[i >> 1] + 1;
    Log[n + 1] = Log[n + 1 >> 1] + 1;

    for (int j = 1; j <= 19; ++j)
     for (int i = 0; i + (1 << j) - 1 <= n; ++i)
       f[i][j] = Min_RMQ(f[i][j - 1], f[i + (1 << j - 1)][j - 1]);
} 

inline int Query_RMQ(int l, int r)
{
    int k = Log[r - l + 1]; 
    return Min_RMQ(f[l][k], f[r - (1 << k) + 1][k]);
}

int main()
{
    n = get(); K = get(); L = get(); R = get();

    for (int i = 1; i <= n; ++i) sum[i] = sum[i - 1] + get();

    Init_RMQ();

    for (int i = 1; i <= n; ++i)
    {
        int x = Max(i - R, 0), y = i - L;
        if (y < 0) continue;
        Q.Push(point(Query_RMQ(x, y), i, x, y));
    }

    while (K--)
    {
        point tmp = Q.g[1]; Q.Pop();
        Ans += sum[tmp.r] - sum[tmp.l];
        if (tmp.x < tmp.l)
         Q.Push(point(Query_RMQ(tmp.x, tmp.l - 1), tmp.r, tmp.x, tmp.l - 1));
        if (tmp.l < tmp.y)
         Q.Push(point(Query_RMQ(tmp.l + 1, tmp.y), tmp.r, tmp.l + 1, tmp.y));
    }

    cout << Ans << endl;
} 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值