BZOJ2288 生日礼物 堆 链表 贪心

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

题目链接

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 cntm,则答案为 s u m sum sum

否则,每次找出绝对值最小的数,将其与序列中相邻两数合并,直至 c n t = m cnt = m cnt=m

若找出的数是负数,则相当于以最小的代价将两段正数连接;

若找出的数是正数,则相当于舍弃该正数,以消除负数的影响。

每次操作均会使 c n t = c n t − 1 cnt = cnt - 1 cnt=cnt1 s u m = s u m − ∣ a [ i ] ∣ sum = sum - |a[i]| sum=suma[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;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值