[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]
,我们都将以下这个三元组放入优先队列当中
其中 L,R 是题目给出的限制条件,处理 R+i−1 时要注意右边界的限制。由于预处理结束之后,我们将不再枚举左端点,所以对于某一个左端点,我们要将右端点的所有情况都考虑进去。
此后的
K
步内,我们依然先取出优先队列顶部的元素计入答案,然后假设右端点是
其中对于第一步的区间最值统计可以直接上线段树求。
预处理复杂度 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