码迷,mamicode.com
首页 > 其他好文 > 详细

bzoj 2006 [NOI2010]超级钢琴 堆 + ST表

时间:2018-01-27 00:40:27      阅读:128      评论:0      收藏:0      [点我收藏+]

标签:long   com   noi2010   turn   ref   csdn   多少   lin   一个   

题目链接

题意

给定一个长度为 \(n\) 的数列 \(a\) ,对于其长度在 \(l\)\(r\) 之间的若干个子区间的 区间和,求最大的 \(k\) 个值的和。

思路

参考自 FZHvampire

首先,预处理出前缀和。

如果要求最大值,很显然的想法是:枚举右端点 \(i\),对于可行的区间 \([i-r+1, i-l+1]\) 求得一个最大值 ,然后\(n\)个最大值中取一个最大的,复杂度 \(NlogN\)

现在要求最大的k个值,怎么办呢?

回想一下,有这么一道趣味题。

将20匹马分成5组(组内有序),每次只能让4匹马赛跑,现要找出跑的最快的4匹马,最少要多少次呢?

做法是,先让各组跑的最快的4匹马比赛,找出最快的踢掉它,然后让该组的第二和其他组的第一跑,再决出第一并踢掉,让该组的下一个上位,依次类推。4次即能找出跑的最快的4匹马。

再想想这道题,好像有那么点联系。

每次找最优的,可以用

最优 则是指区间和最大。

每次踢掉最优的让下一个上位 又怎么体现呢?

维护一个元素为五元组 \(\{i, l, r, p, val\}\)的堆,意为:
左端点在 \([l, r]\) 区间内,右端点为 \(i\) 的这些区间中,最大的区间和 在 左端点为 \(p\) 时 取到,区间和为 \(val\).

那么 踢掉最优的 即为 取 pq.top()
让下面的上位 即为 将这段区间拆成 除中间最优点外 的 两部分 重新插入到堆中,即插入 [l, p-1] 段与 [p+1, r] 段。

看题解时觉得是很巧妙的做法了,按照上面的思路仔细梳理一下,其实自己也是可以想出来的。

Code

#include <bits/stdc++.h>
#define maxn 500010
using namespace std;
typedef long long LL;
int a[maxn], n;
LL pre[maxn];
struct node { LL x; int p; }mn[maxn][32];
struct qnode {
    int i, l, r, p; LL val;
    bool operator < (const qnode& nd) const {
        return val < nd.val;
    }
};
priority_queue<qnode> pq;
void rmqInit() {
    for (int i = 1; i <= n; ++i) mn[i][0] = {pre[i], i};
    for (int j = 1; (1 << j) <= n; ++j) {
        for (int i = 1; i + (1 << (j-1)) - 1 <= n; ++i) {
            mn[i][j] = mn[i][j-1].x < mn[i + (1<<(j-1))][j-1].x ? mn[i][j-1] : mn[i + (1<<(j-1))][j-1];
        }
    }
}
int query(int l, int r) {
    int k = (int)(log((double)r-l+1)/log((double)2));
    return mn[l][k].x < mn[r-(1<<k)+1][k].x ? mn[l][k].p : mn[r-(1<<k)+1][k].p;
}
int main() {
    int k, l, r;
    scanf("%d%d%d%d", &n, &k, &l, &r);
    pre[1] = 0;
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]), pre[i+1] = pre[i] + a[i];
    rmqInit();
    for (int i = l; i <= n; ++i) {
        int ll = max(1, i - r + 1), rr = i - l + 1,
            p = query(ll, rr);
        pq.push({i, ll, rr, p, pre[i+1] - pre[p]});
    }
    LL ans = 0;
    while (k--) {
        qnode qnd = pq.top(); pq.pop();
        ans += qnd.val;
        int l1 = qnd.l, r1 = qnd.p - 1,
            l2 = qnd.p + 1, r2 = qnd.r,
            i = qnd.i;
        if (l1 <= r1) {
            int p1 = query(l1, r1);
            pq.push({i, l1, r1, p1, pre[i+1] - pre[p1]});
        }
        if (l2 <= r2) {
            int p2 = query(l2, r2);
            pq.push({i, l2, r2, p2, pre[i+1] - pre[p2]});
        }
    }
    printf("%lld\n", ans);
    return 0;
}

bzoj 2006 [NOI2010]超级钢琴 堆 + ST表

标签:long   com   noi2010   turn   ref   csdn   多少   lin   一个   

原文地址:https://www.cnblogs.com/kkkkahlua/p/8361808.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!