[BZOJ2006][[NOI2010]超级钢琴][优先队列+线段树]

版权声明:禁止转载 https://blog.csdn.net/g1n0st/article/details/56669953

[BZOJ2006][[NOI2010]超级钢琴][优先队列+线段树]

题目大意:

给定一个长度为N(≤500,000)的序列,求K个本质不同的长度大于L小于R的序列的序列和的总和的最大值。两个序列本质不同当且仅当两个序列内元素的集合不同。

思路:

一开始觉得这题是道直接贪心的傻逼题,然后突然发现序列中的元素存在负数。

WA

虽然这道题很皮,但是基本的思路还是不变的。

假设题意不变,使得序列内的元素都是非负整数,我们可以先将子序列{[1,n],[2,n],,[n1,n],[n,n]}都存入一个优先队列当中,每次从优先队列中取出区间和最大的子序列计入答案。假设取出的序列是[i,j],那么再将[i,j-1]重新扔回队列。可以看出,对于原序列的子序列,左端点的变化已经在第一次操作中全部存入优先队列,[i,j]的另一个长度-1的子序列[i+1,j],必定可以由[i,n]这个最先存入的序列不断变化得到,因此我们只需要在取出的过程中枚举右端点即可。

扩展到这道题,我们可以开一个三元组(i,L,R),表示序列左端点在i时,右端点在[L,R]上下限内的最优解。
同样在第一步中对于每个i[1,nl+1],我们都将以下这个三元组放入优先队列当中

(i,L+i1,Min(R+i1,n))

其中L,R是题目给出的限制条件,处理R+i1时要注意右边界的限制。由于预处理结束之后,我们将不再枚举左端点,所以对于某一个左端点,我们要将右端点的所有情况都考虑进去。

此后的K步内,我们依然先取出优先队列顶部的元素计入答案,然后假设右端点是x[L,R],我们只要把以下两个三元组存入优先队列即可(仍需考虑x1>=Lx+1<=R

(i,L,x1),(i,x+1,R)

其中对于第一步的区间最值统计可以直接上线段树求。

预处理复杂度O(nlogn),K次弹出堆顶元素和线段树查询 O(klogn),总复杂度O((n+k)logn)

代码:

#include <bits/stdc++.h>
const int Maxn = 500010;
using namespace std;
inline char get(void) {
    static char buf[1000000], *p1 = buf, *p2 = buf;
    if (p1 == p2) {
        p2 = (p1 = buf) + fread(buf, 1, 1000000, stdin);
        if (p1 == p2) return EOF;
    }
    return *p1++;
}
inline void read(int &x) {
    x = 0; static char c; bool minus = false;
    for (; !(c >= '0' && c <= '9'); c = get()) if (c == '-') minus = true;
    for (; c >= '0' && c <= '9'; x = x * 10 + c - '0', c = get()); if (minus) x = -x;
}
int a[Maxn];
long long ans;
struct H {
#define c(x) const int &x
    int m, w, l, ll, rl;
    H(void) {}
    H(c(m), c(w), c(l), c(ll), c(rl)) : m(m), w(w), l(l), ll(ll), rl(rl) {}
    friend bool operator < (const H &a, const H &b) {
        return a.m < b.m;
    }
} x;
priority_queue<H> qu;
struct S {
    int m, w;
    S(void) { m = -(1 << 30); }
    friend bool operator > (const S &a, const S &b) {
        return a.m > b.m;
    }
} tmp;
S t[Maxn << 2];
inline S Max(const S &a, const S &b) {
    return a > b ? a : b;
}
inline int Min(const int &a, const int &b) {
    return a < b ? a : b;
}
inline void build(int o, int l, int r) {
    if (l == r) {
        t[o].m = a[l];
        t[o].w = l;
        return;
    }
    int mid = (l + r) >> 1;
    build(o << 1, l, mid);
    build(o << 1 | 1, mid + 1, r);
    t[o] = Max(t[o << 1], t[o << 1 | 1]);
}
inline S ask(int o, int l, int r, int L, int R) {
    if (l >= L && r <= R) return t[o];
    int mid = (l + r) >> 1; S a1, a2;
    if (mid >= L) a1 = ask(o << 1, l, mid, L, R);
    if (mid < R) a2 = ask(o << 1 | 1, mid + 1, r, L, R);
    return Max(a1, a2);
}
int n, k, l, r;
int main(void) {
    //freopen("in.txt", "r", stdin);
    //freopen("out.txt", "w", stdout);
    read(n), read(k), read(l), read(r);
    for (int i = 1; i <= n; i++) {
        read(a[i]);
        a[i] += a[i - 1];
    }
    build(1, 1, n);
    int ll, rr;
    for (int i = 0; i < n - l + 1; i++) {
        ll = i + l, rr = Min(i + r, n);
        tmp = ask(1, 1, n, ll, rr);
        qu.push(H(tmp.m - a[i], tmp.w, i + 1, ll, rr));
    }
    while (k--) {
        x = qu.top(); qu.pop();
        ans += x.m;
        if (x.w - 1 >= x.ll) {
            tmp = ask(1, 1, n, x.ll, x.w - 1);
            qu.push(H(tmp.m - a[x.l - 1], tmp.w, x.l, x.ll, x.w - 1));
        }
        if (x.w + 1 <= x.rl) {
            tmp = ask(1, 1, n, x.w + 1, x.rl);
            qu.push(H(tmp.m - a[x.l - 1], tmp.w, x.l, x.w + 1, x.rl));
        }
    }
    printf("%lld", ans);
    return 0

然而跑得并不是很快,由于区间最值不带修改,可以直接上ST表对快很多。

完。
By g1n0st

阅读更多
换一批

没有更多推荐了,返回首页