超级钢琴 (堆 + RMQ)

博客围绕超级钢琴问题展开,该问题要求用k个不同超级和弦创作乐曲并求最大美妙度。解题先使用前缀和计算区间和,再用贪心思想,借助RMQ或线段树快速求最大值,用堆存储解,每次选堆顶元素,提取后将剩余区间放回堆,避免重复并保留较优解。

题目描述

小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,表示按编号从小到大每个音符的美妙度。

 

输出格式:

 

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

 

输入输出样例

输入样例#1: 复制

4 3 2 3
3
2
-6
8

输出样例#1: 复制

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。

所有数据满足:-1000 ≤ Ai ≤ 1000,1 ≤ L ≤ R ≤ n且保证一定存在满足要求的乐曲。

题解:

首先可以看到,每段超级和弦都是连续的,美妙度是这段区间内所有美妙度的和。可以想到,每次求解区间和显然是不合算的,所以考虑到用前缀和。

考虑暴力,我们需要把所有满足条件的字段抽出来排个序,但这实在是不可想象。所以考虑使用贪心思想来解决这个问题。

先想预处理。我们定义 MAX(o, l, r) = max {sum(pos) - sum(o - 1) ∣ l ≤ pos ≤ r} ,即以 o 为左端点,右端点范围是 [l,r]的最大子段。求 sum() 就用前面说的前缀和。可以看出,o 的位置是固定的。所以 sum(o - 1) 也是固定的。所以我们要求这个的最大值,只要 sum(pos) 最大就可以了。即要求 sum(pos) 在[l,r] 中的最大值,那怎么快速地求出这个最大值呢?很显然,这不是 RMQ 或者线段树的活么。

接下来想怎么贪心。我们可以每次都选最优的子段,这样选 k 次显然就是我们所要的结果,那怎么找到最优解呢?用堆来将解存进去,每次堆顶的元素就是最优解。

考虑一个三元组 (o, l, r) 表示以 o 为左端点,右端点的选择区间为 [l,r] 的 情况,我用了一个 struct 来表示这个三元组,但往往实际上每个情况需要额外记录这个情况的最优解 pos ,这个不麻烦,在 struct 里面敲一个构造函数就可以了。

我们假设当前最大的三元组是 (o, l, r) ,最优解位置是 pos ,ans 累加这个三元组的贡献。由于 pos 已经被选中,对于这个 o, pos 已经不能重复选中,但最优解还可能存在于 pos 左右的两端区间中,所以提取出 (o, l, r) 之后,为了避免重复且不丧失其他较优解,我们仍然要把 (o, l, pos - 1) , (o, pos+ 1, r) 扔回堆里面去。

最后实现的时候还要注意一点,RMQ 原本数组里面记录的是最优解的值,但我们查询区间最大值的时候查询的是最优解的位置。所以数组里面存的是最优解的位置,要特殊处理。

#include<bits/stdc++.h>
//#include<unordered_map>
//#include<unordered_set>
#include<iostream>
#include<sstream>
#include<iterator>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<set>
#include<vector>
#include<bitset>
#include<climits>
#include<queue>
#include<iomanip>
#include<cmath>
#include<stack>
#include<map>
#include<ctime>
#include<new>
using namespace std;
#define LL long long
#define ULL unsigned long long
#define MT(a, b) memset(a,b,sizeof(a))
const int INF  =  0x3f3f3f3f;
const int O    =  1e6;
const int mod  =  1e7+3;
const int maxn =  5e5+3;
const double PI  =  acos(-1.0);
const double E   =  2.718281828459;
const double eps = 1e-8;

int n, k, l , r;
int a[maxn], sum[maxn] = { 0 }, dp[maxn][23];

void RMQ() {
    for(int i=1; i<=n; i++) dp[i][0] = i;
    for(int j=1; j<23; j++) {
        for (int i = 1; i + (1 << j) - 1 <= n; i++) {
            int px = dp[i][j - 1], py = dp[i + (1 << (j - 1))][j - 1];
            dp[i][j] = sum[px] > sum[py] ? px : py;
        }
    }
}

int query(int l, int r) {
    int e = 0;
    while(l + (1 << (e + 1) ) - 1 <= r ) e ++;
    int px = dp[l][e], py = dp[r-(1<<e)+1][e];
    return sum[px] > sum [py] ? px : py;
}

struct dd {
    int o, l, r, pos;
    dd(){};
    dd (int O, int L, int R) : o(O), l(L), r(R) { pos = query(L, R); }
    bool friend operator < (dd a, dd b) {
        return sum[a.pos] - sum[a.o - 1] < sum[b.pos] - sum[b.o - 1];
    }
} h[maxn << 2];

struct Binary_Heap{
    int HeapSize;
    Binary_Heap () { HeapSize = 0; }
    inline void push(dd v){
        int u = ++HeapSize, fa; h[HeapSize] = v;
        while(u > 1){
            fa = u >> 1;
            if(h[u] < h[fa]) break;
            swap(h[u], h[fa]);
            u = fa;
        }
    }
    inline dd top(){  return h[1]; }
    inline void pop(){
        int u = 1; h[1] = h[HeapSize--];
        while((u << 1) <= HeapSize){
            int Son = u << 1;
            if(Son + 1 <= HeapSize && h[Son] < h[Son + 1]) Son++;
            if(h[Son] < h[u]) break;
            swap(h[u], h[Son]);
            u = Son;
        }
    }
};

int main(){

    scanf("%d%d%d%d", &n, &k, &l, &r);
    for(int i=1; i<=n; i++) { scanf("%d", &a[i]); sum[i] = sum[i-1] + a[i]; }
    RMQ();

    Binary_Heap res;
    for(int i=1; i+l-1 <= n; i++) res.push( dd(i, i+l-1, min(i+r-1, n) ) );

    LL ans = 0;
    while(k --) {
        dd now = res.top(); res.pop();
        ans += sum[now.pos] - sum[now.o-1];
        if(now.pos - 1 >= now.l) res.push( dd(now.o, now.l, now.pos-1) );
        if(now.pos + 1 <= now.r) res.push( dd(now.o, now.pos+1, now.r) );
    }
    printf("%lld\n", ans);
    return 0;
}

ps :手写堆的感觉是真的爽

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值