您的位置:首页 > 其它

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

2017-02-23 13:25 537 查看

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

题目大意:

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

思路:

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



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

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

扩展到这道题,我们可以开一个三元组(i,L,R),表示序列左端点在i时,右端点在[L,R]上下限内的最优解。

同样在第一步中对于每个i∈[1,n−l+1],我们都将以下这个三元组放入优先队列当中

(i,L+i−1,Min(R+i−1,n))

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

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

(i,L,x−1),(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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息