bzoj 2006 [NOI2010]超级钢琴 贪心+RMQ+堆

题面

题目传送门

解法

感觉这道题解法妙妙的

考虑这样一个贪心,记一个三元组\((x,l,r)\)表示当前和弦的左端点为\(x\),右端点在区间\([l,r]\)

那么这个三元组对应的和弦最大值为\(max(s_l,…s_r)-s_{x-1}\),其中,\(s_i\)表示前缀和

考虑将这些三元组扔进一个大根堆里,按照和弦的大小作为关键字

然后取出堆顶的时候,找到\(s_l\)\(s_r\)中最大的前缀和对应位置\(pos\),向堆中加入新的2个三元组\((x,l,pos-1),(x,pos+1,r)\)

然后不断这样做就可以了

查询区间最大值可以用RMQ实现\(O(1)\)查询

时间复杂度:\(O(n\ log\ n+k\ log\ n)\)

代码

#include <bits/stdc++.h>
#define int long long
#define N 500010
using namespace std;
template <typename node> void chkmax(node &x, node y) {x = max(x, y);}
template <typename node> void chkmin(node &x, node y) {x = min(x, y);}
template <typename node> void read(node &x) {
    x = 0; int f = 1; char c = getchar();
    while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();}
    while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f;
}
struct Node {
    int x, l, r, val;
    bool operator > (const Node &a) const {
        return val < a.val;
    }
};
int f[N][21], g[N][21];
int calc(int l, int r) {
    int t = log2(r - l + 1);
    return max(f[l][t], f[r - (1 << t) + 1][t]);
}
int Find(int l, int r) {
    int t = log2(r - l + 1);
    if (f[l][t] > f[r - (1 << t) + 1][t]) return g[l][t];
    return g[r - (1 << t) + 1][t];
}
main() {
    int n, m, l, r;
    read(n), read(m), read(l), read(r);
    for (int i = 1; i <= n; i++) {
        int x; read(x);
        f[i][0] = f[i - 1][0] + x, g[i][0] = i;
    }
    for (int j = 1; (1 << j) <= n; j++)
        for (int i = 1; i + (1 << j) - 1 <= n; i++) {
            int x = f[i][j - 1], y = f[i + (1 << j - 1)][j - 1];
            f[i][j] = max(x, y);
            if (x > y) g[i][j] = g[i][j - 1];
                else g[i][j] = g[i + (1 << j - 1)][j - 1];
        }
    priority_queue <Node, vector <Node>, greater <Node> > h;
    for (int i = 1; i <= n - l + 1; i++) {
        int tl = i + l - 1, tr = min(n, i + r - 1);
        int val = calc(tl, tr);
        h.push((Node) {i, tl, tr, val - f[i - 1][0]});
    }
    int ans = 0;
    while (m--) {
        Node tmp = h.top(); h.pop();
        int tl = tmp.l, tr = tmp.r;
        int pos = Find(tl, tr); ans += tmp.val;
        if (pos != tl) h.push((Node) {tmp.x, tl, pos - 1, calc(tl, pos - 1) - f[tmp.x - 1][0]});
        if (pos != tr) h.push((Node) {tmp.x, pos + 1, tr, calc(pos + 1, tr) - f[tmp.x - 1][0]});
    }
    cout << ans << "\n";
    return 0;
}

转载于:https://www.cnblogs.com/copperoxide/p/9477940.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值