[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
相关文章推荐
- bzoj2006 [NOI2010]超级钢琴
- BZOJ2006: [NOI2010]超级钢琴
- 【BZOJ 2006】2006: [NOI2010]超级钢琴(RMQ+优先队列)
- bzoj 2006 [NOI2010]超级钢琴 二分答案 可持久化线段树
- bzoj2006: [NOI2010]超级钢琴 贪心+堆
- BZOJ2006: [NOI2010]超级钢琴
- BZOJ2006 [NOI2010]超级钢琴
- BZOJ 2006 NOI2010 超级钢琴 划分树+堆
- bzoj2006 [NOI2010]超级钢琴
- 【BZOJ】2006 [NOI2010]超级钢琴 ST表+堆+贪心
- bzoj2006 [NOI2010]超级钢琴 堆+ST表/主席树
- BZOJ2006:[NOI2010]超级钢琴——题解
- bzoj 2006: [NOI2010]超级钢琴 可持久化线段树+优先队列
- 【BZOJ 2006】 [NOI2010]超级钢琴
- BZOJ2006: [NOI2010]超级钢琴
- BZOJ 2006: [NOI2010]超级钢琴
- [BZOJ2006][NOI2010]超级钢琴(ST表+堆)
- BZOJ2006 [NOI2010]超级钢琴
- Bzoj2006: [NOI2010]超级钢琴
- bzoj2006 [NOI2010]超级钢琴