题解:P2048 [NOI2010] 超级钢琴【思维贪心+数据结构】

· · 题解

发现区间不定长,这就很讨厌。

暴力

考虑对于每个以 i 为左端点的区间,因为长度范围 [L,R],所以右端点范围为 [i+L-1,i+R-1]

不难想到区间和可以用前缀和数组 s 来快速取得。

考虑暴力存储所有的区间和再排序,时间复杂度可以达到 O(n^2 \log n)

正解

书接上回,考虑对于每个以 i 为左端点的区间,如果只取一次,那么它能取到的最大值应该是 \max\{s[x]\} - s[i-1],\ x \in [i+l-1,i+r-1]。发现每个 s[x] 都必须减去 s[i-1],那么比大小就成了纯比较 s[x]。不难想到可以用 ST 表维护任意区间中的最大前缀和以及位置(代码实现得很妙)。

现在我们能快速取得最值信息了,下面就思考贪心策略:怎样取最优?

首先对于最大美妙度,如果把之前预处理出来的每个 i 的信息都放到大根堆里,那么排序后堆顶肯定就是最大的。

那第二大呢?我们可以破开最大的,假设最大值的前缀和为 s_t,将原本的 [i,t] 删掉,再把 [i,(i+L-1) \sim (t-1)][i, (w+1) \sim (i+R-1)] 中的最大值入堆。

破开依据:“两个超级和弦被认为是相同的,当且仅当这两个超级和弦所包含的音符集合是相同的”。

那这样贪心为什么是对的呢?好像很显然。 因为一个 i 为左端点的最大值摘出去后,i 可能还会作为其他有用值的左端点,所以再放回去就很对。

这样就解决了,考虑把状态 (i,l,r,t) 表示左端点为 i,右端点可以在 [l,r] 范围内,前缀和最大值的下标为 t

--- **这种问题的通法:建立集合 $S$,使当前剩余最大值一定存在于 $S$ 中,且使得将最大值排除后,集合性质不变。** ```cpp #include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 5e5 + 7; int n, k, L, R, st[maxn][30]; // 这里的st中存的是最大前缀和的下标 ll s[maxn]; void init(){ for(int i = 1; i <= n; i ++) st[i][0] = i; for(int j = 1; j <= log2(n); j ++){ for(int i = 1; i + (1 << j) - 1 <= n; i ++){ int a = st[i][j - 1], b = st[i + (1 << (j - 1))][j - 1]; st[i][j] = s[a] > s[b]? a : b; // 比较值的大小,存下标即可 } } } int query(int l, int r){ int k = log2(r - l + 1); int a = st[l][k], b = st[r - (1 << k) + 1][k]; return s[a] > s[b]? a : b; } struct Node{ int i, l, r, t; bool operator < (Node p) const{ return s[t] - s[i - 1] < s[p.t] - s[p.i - 1]; // 注意堆默认大顶堆,这里重载是小于号,排序规则反过来 } }; priority_queue<Node> Q; void solve(){ cin >> n >> k >> L >> R; for(int i = 1; i <= n; i ++){ cin >> s[i]; s[i] += s[i - 1]; // 直接输入前缀和数组即可 } init(); for(int i = 1; i + L - 1 <= n; i ++){ int l = i + L - 1, r = min(n, i + R - 1); Q.push({i, l, r, query(l, r)}); } ll ans = 0; while(k --){ auto [i, l, r, t] = Q.top(); Q.pop(); ans += s[t] - s[i - 1]; if(l != t) Q.push({i, l, t - 1, query(l, t - 1)}); if(r != t) Q.push({i, t + 1, r, query(t + 1, r)}); // 破开当前元素,重新放入堆中 } cout << ans << '\n'; } signed main(){ ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0); solve(); return 0; } ```