题目描述

小Z是一个小有名气的钢琴家,最近C博士送给了小Z一架超级钢琴,小Z希望能够用这架钢琴创作出世界上最美妙的音乐。

这架超级钢琴可以弹奏出 个音符,编号为 。第 个音符的美妙度为 ,其中 可正可负。

一个“超级***”由若干个编号连续的音符组成,包含的音符个数不少于 且不多于 。我们定义超级***的美妙度为其包含的所有音符的美妙度之和。两个超级***被认为是相同的,当且仅当这两个超级***所包含的音符集合是相同的。

小Z决定创作一首由k个超级***组成的乐曲,为了使得乐曲更加动听,小Z要求该乐曲由k个不同的超级***组成。我们定义一首乐曲的美妙度为其所包含的所有超级***的美妙度之和。小Z想知道他能够创作出来的乐曲美妙度最大值是多少。

<!--more-->

输入输出格式

输出格式

输入第一行包含四个正整数 。其中 为音符的个数, 为乐曲所包含的超级***个数,分别是超级***所包含音符个数的下限和上限。

接下来 行,每行包含一个整数,表示按编号从小到大每个音符的美妙度。

输入格式

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

输入输出样例

输入样例

4 3 2 3
3
2
-6
8

输出样例

11

说明

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。

数据范围

所有数据满足: 且保证一定存在满足要求的乐曲。

题目链接

思路

暴力解法

整理一下这个题的题意,其实就是求序列长度在 范围内的的前 大序列的和。

最暴力的想法当然是枚举序列的长度,然后枚举端点,再维护一个记录前k大序列值的数组,每次求到一个新的序列和,暴力更新。

既然是最暴力的想法,当然可以优化,而这道题优化之后就能得到正解。

优化

优化一

既然是求序列长度和,聪明的你有没有想到什么奇技淫巧呢?

这...这不是前缀和吗?

我们可以预处理一个前缀和的数组,这样知道长度和端点之后就可以 地求序列和了。

优化二

暴力解法中,有提到枚举区间长度和端点对吧?

我们可以进行另一个优化。

先枚举左端点,这样我们就可以根据左端点的下标和给出的 来求出右端点的范围了。

根据前缀和的定义,已知左端点之后,只需要最大化从 到右端点的前缀和即可。

然而我们已经知道右端点的范围了,那么这就是一个 问题。可以用代码简单的 表来解决这个问题,只需进行 复杂度的预处理,即可进行 时间复杂度的查询。 <del>你非要写线段树也不拦着你。 </del>

优化三

对于每次通过枚举端点求出的序列,定义一个四元组 , 分别表示左右端点的下标, 表示右端点的范围。

现在的暴力算法,只剩下求前 大的序列这个操作没有优化过了。实际上,动态求前 大,堆这种数据结构是再方便不过了,况且 库里还有优先队列这种方便的东西。

利用堆以区间长度为关键字,在堆中维护这些四元组。此时的堆为大根堆。

整理

这样的话,先枚举序列的,再利用求在右端点范围内的前缀和最大值,再用堆维护优化三中提到的四元组。

然后,边弹出堆顶,边插入新的四元组 。其中 表示区间内前缀和最大的端点的下标。因为如果不插入的话只是以每个点为左端点求最大值,然而这 个序列的左端点可以重复,所以需要把这个右端点扣掉后再压入堆。弹出堆顶 次,和即为答案。这一步

代码

#include <cstdio>
#include <iostream>
#include <cmath>
#include <climits>
#include <cstring>
#include <queue>
#define DEBUG(x) std::cerr << #x << " = " << x << endl

const int MAXN = 500000 + 7;

int f[MAXN][22], sum[MAXN];
int n, k, L, R;
long long ans;

struct Node {
    int s, l, r, t;

    bool operator < (const Node &x) const {
        return sum[t] - sum[s - 1] < sum[x.t] - sum[x.s - 1];
    }
};

std::priority_queue<Node> heap;

inline int mx(int x, int y) {
    return sum[x] > sum[y] ? x : y;
}

inline int query(int l, int r) {
    if(l > r) return -1;
    int k = std::log(r - l + 1) / std::log(2);
    return mx(f[l][k], f[r - (1 << k) + 1][k]);
}

int main(int argc, char *argv[]) {
    scanf("%d %d %d %d", &n, &k, &L, &R);
    int w;
    for (int i = 1; i <= n; i++) {
        scanf("%d", &w);
        sum[i] = sum[i - 1] + w;
        f[i][0] = i;
    }
    for (int j = 1; j <= 19; j++) {
        for (int i = 1; i + (1 << j) - 1 <= n; i++) {
            f[i][j] = mx(f[i][j - 1],  f[i + (1 << (j - 1))][j - 1]);
        }
    }
    for (int i = 1; i + L - 1 <= n; i++) {
        int right = std::min(n, i + R - 1);
        heap.push((Node) {i, i + L - 1, right, query(i + L - 1, right)});
    }
    int cnt = 0;
    while (cnt < k) {
        cnt++;
        Node g = heap.top(); heap.pop();
        ans += sum[g.t] - sum[g.s - 1];
        int q1 = query(g.l, g.t - 1);
        int q2 = query(g.t + 1, g.r);
        if(~q1) heap.push((Node) {g.s, g.l, g.t - 1, q1});
        if(~q2) heap.push((Node) {g.s, g.t + 1, g.r, q2});
    }
    printf("%lld\n", ans);
    return 0;
}