51Nod1115 最大M子段和V3 (可撤销贪心 + 堆 + 双向链表)
Description
给的一个长度为 n n n 的环形序列 a a a,求从中选 m m m 个可以为空的子段的最大和。
2 ≤ n , m ≤ 1 0 5 , − 1 0 9 ≤ a i ≤ 1 0 9 2 \leq n, m \leq 10^5,-10^9 \leq a_i \leq 10^9 2≤n,m≤105,−109≤ai≤109。
Solution
先想到的可能是断环为链后 d p dp dp, f i , j f_{i,j} fi,j 表示划分了 i i i 段,最后一段以 j j j 结尾的最大和。每个数要么新开一段,要么加入第 j j j 段。转移时维护前缀最大值。然后滚动数组优化空间。可是时间爆炸。
dp 做不了考虑贪心
删掉 0 0 0,那么 a a a 只有正数和负数。将连续的正数加起来,连续的负数加起来,得到新的正负交替的环形序列 a a a。最好是选所有的正数,但是正数的个数 可能 > m >m >m。那么减少正数个数有两个方法
- 删一个整数,减少的值为这个正数,减少了一段。
- 将一个负数与它两边的正数合并,减少的值为负数的相反数,两端变一段。
假设正数的个数为
k
∧
k
>
m
k \land k > m
k∧k>m。将所有的正数变为负数,问题转换成正数之和
+
a
+ \ a
+ a 中
k
−
m
k-m
k−m 个不相邻的数的最大和。考虑如何在
O
(
n
log
n
)
O(n \log n)
O(nlogn) 内求最大和。我会时间爆炸的 dp!。
dp 做不了考虑贪心
可撤销贪心。用大根堆维护数字和下标,用双向链表维护 a a a。每次取堆首 a x a_x ax,但可能取 a x a_x ax 的前驱 a p r e a_{pre} apre 和后继 a n x t a_{nxt} anxt 更优。
所以将 a p r e ↔ a x ↔ a n x t a_{pre} \leftrightarrow a_x \leftrightarrow a_{nxt} apre↔ax↔anxt 换成 a x = a p r e − a x + a n x t a_x = a_{pre} - a_x + a_{nxt} ax=apre−ax+anxt,并且标记 a p r e a_{pre} apre 和 a n x t a_{nxt} anxt 不能选了。将新的 a x a_x ax 放回堆中。如果取 p r e pre pre 和 n x t nxt nxt 更优,那么下一次会从堆首取出新的 a x a_x ax,而 a x + a p r e − a x + a n x t = a p r e + a n x t a_x + a_{pre} - a_x + a_{nxt} = a_{pre} + a_{nxt} ax+apre−ax+anxt=apre+anxt。
进行 m m m 次这样的操作,最后取出的 a x a_x ax 之和为答案。
不要忘记 long long。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 5, INF = 0x3f3f3f3f;
inline int read() {
int x = 0, f = 0;
char ch = 0;
while (!isdigit(ch)) f |= ch == '-', ch = getchar();
while (isdigit(ch)) x = (x << 3) + (x << 1) + (ch ^ 48), ch = getchar();
return f ? -x : x;
}
int _n, m, n, cnt;
ll a[N], ans = 0;
int vis[N], pre[N], nxt[N];
priority_queue<pair<ll, int> > q;
int main() {
scanf("%d%d", &_n, &m);
for (int i = 1; i <= _n; i++) {
int x = read();
if (!n || (x >= 0) != (a[n] >= 0)) a[++n] = x;
else a[n] += x;
}
if ((a[1] >= 0) == (a[n] >= 0)) a[1] += a[n--];
for (int i = 1; i <= n; i++) {
pre[i] = i - 1, nxt[i] = i + 1;
if (a[i] >= 0) ans += a[i], a[i] = -a[i], cnt++;
q.push(make_pair(a[i], i));
}
nxt[n] = 1, pre[1] = n;
m = cnt - m;
if (m <= 0) {
printf("%lld\n", ans); return 0;
}
while (m--) {
int x = q.top().second; q.pop();
if (vis[x]) {
m++; continue;
}
ans += a[x];
a[x] = a[pre[x]] + a[nxt[x]] - a[x];
q.push(make_pair(a[x], x));
nxt[pre[pre[x]]] = pre[nxt[nxt[x]]] = x;
vis[pre[x]] = vis[nxt[x]] = 1;
pre[x] = pre[pre[x]], nxt[x] = nxt[nxt[x]];
}
printf("%lld\n", max(0ll, ans));
return 0;
}

浙公网安备 33010602011771号