Description
给的一个长度为 的环形序列 ,求从中选 个可以为空的子段的最大和。
。
Solution
先想到的可能是断环为链后 , 表示划分了 段,最后一段以 结尾的最大和。每个数要么新开一段,要么加入第 段。转移时维护前缀最大值。然后滚动数组优化空间。可是时间爆炸。
dp 做不了考虑贪心
删掉 ,那么 只有正数和负数。将连续的正数加起来,连续的负数加起来,得到新的正负交替的环形序列 。最好是选所有的正数,但是正数的个数 可能 。那么减少正数个数有两个方法
- 删一个整数,减少的值为这个正数,减少了一段。
- 将一个负数与它两边的正数合并,减少的值为负数的相反数,两端变一段。
假设正数的个数为
。将所有的正数变为负数,问题转换成正数之和
中
个不相邻的数的最大和。考虑如何在
内求最大和。我会时间爆炸的 dp!。
dp 做不了考虑贪心
可撤销贪心。用大根堆维护数字和下标,用双向链表维护 。每次取堆首 ,但可能取 的前驱 和后继 更优。
所以将 换成 ,并且标记 和 不能选了。将新的 放回堆中。如果取 和 更优,那么下一次会从堆首取出新的 ,而 。
进行 次这样的操作,最后取出的 之和为答案。
不要忘记 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;
}