题目链接
https://www.lydsy.com/JudgeOnline/problem.php?id=2288
分析
显然符号相同的一段会一起被选,因此先合并符号相同的各段,最终得到正数负数相间的序列。
设此时有 c n t cnt cnt 个正数,且其和为 s u m sum sum,若 c n t ≤ m cnt \leq m cnt≤m,则答案为 s u m sum sum。
否则,每次找出绝对值最小的数,将其与序列中相邻两数合并,直至 c n t = m cnt = m cnt=m。
若找出的数是负数,则相当于以最小的代价将两段正数连接;
若找出的数是正数,则相当于舍弃该正数,以消除负数的影响。
每次操作均会使 c n t = c n t − 1 cnt = cnt - 1 cnt=cnt−1, s u m = s u m − ∣ a [ i ] ∣ sum = sum - |a[i]| sum=sum−∣a[i]∣, a [ i ] a[i] a[i] 为选出的值。
这也可以解释为什么每次取出绝对值最小的数。
可以用堆和链表来实现,若取出的是负数且处于链表边界,则直接舍弃即可。
AC代码
#include <cstdio>
#include <cmath>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
int num = 0, flag = 1;
char c = getchar();
while (c < '0' || c > '9') {
if (c == '-') flag = -1;
c = getchar();
}
while (c >= '0' && c <= '9')
num = num * 10 + c - '0', c = getchar();
return flag * num;
}
const int maxn = 1e5 + 5;
struct Node1 {
int v, p;
bool operator < (const Node1& rhs) const {
return abs(v) < abs(rhs.v);
}
};
struct Node2 {
int v, l, r, p;
} list[maxn];
struct Heap {
int tot;
Node1 h[maxn];
Heap(int t = 0) : tot(t) {}
void up(int p) {
while (p > 1) {
if (h[p] < h[p / 2]) {
swap(h[p], h[p / 2]);
swap(list[h[p].p].p, list[h[p / 2].p].p);
p /= 2;
} else break;
}
}
void down(int p) {
while (2 * p <= tot) {
int q = 2 * p;
if (q + 1 <= tot && h[q + 1] < h[q]) ++q;
if (h[q] < h[p]) {
swap(h[q], h[p]);
swap(list[h[q].p].p, list[h[p].p].p);
p = q;
} else break;
}
}
void push(Node1 x) {
h[++tot] = x;
up(tot);
}
void pop(int p = 1) {
swap(h[p], h[tot]);
swap(list[h[p].p].p, list[h[tot--].p].p);
up(p), down(p);
}
} heap;
inline void del(int x) {
list[list[x].l].r = list[x].r;
list[list[x].r].l = list[x].l;
}
int a[maxn], tot;
int main() {
int n = read(), m = read(), cnt = 0, sum = 0;
for (int i = 1; i <= n; ++i) {
int x = read();
if (!x) continue;
if (tot && 1ll * x * a[tot] > 0) a[tot] += x;
else a[++tot] = x;
}
for (int i = 1; i <= tot; ++i) {
if (a[i] > 0) ++cnt, sum += a[i];
list[i].v = a[i], list[i].l = i - 1, list[i].r = i + 1;
list[i].p = heap.tot + 1;
Node1 x;
x.v = a[i], x.p = i;
heap.push(x);
}
if (cnt <= m) {
printf("%d", sum);
return 0;
}
while (cnt > m) {
Node1 x = heap.h[1];
heap.pop();
Node2& y = list[x.p];
if (y.v < 0 && (!y.l || y.r == tot + 1)) continue;
--cnt, sum -= abs(y.v);
if (!y.l) {
y.v += list[y.r].v;
heap.pop(list[y.r].p);
del(y.r);
}
else if (y.r == tot + 1) {
y.v += list[y.l].v;
heap.pop(list[y.l].p);
del(y.l);
}
else {
y.v += list[y.l].v + list[y.r].v;
heap.pop(list[y.l].p), heap.pop(list[y.r].p);
del(y.l), del(y.r);
}
x.v = y.v;
heap.push(x);
}
printf("%d", sum);
return 0;
}

本文深入分析了一道算法竞赛题目,讲解了如何通过合并相同符号的数段,优化选择过程,以达到最小化操作次数的目标。文章详细介绍了使用堆和链表实现的高效算法,并提供了完整的AC代码。
234

被折叠的 条评论
为什么被折叠?



