AcWing 163 贪心 + 链表 + 二叉堆

题意

传送门 AcWing 163 生日礼物

题解

预处理出连续的非负与负数部分,得到非负数与负数连续、交替的数列 a a a。当没有 M M M 的限制时,答案为所有连续的非负部分(设其数量为 c n t cnt cnt)的和 s u m sum sum。考虑 M M M 的限制,若 c n t ≤ M cnt\leq M cntM,则答案为 s u m sum sum;反之,需要考虑将选择的连续部分减少 K , K = c n t − M K,K=cnt-M K,K=cntM,并处理 s u m sum sum 中对应的贡献。

K = 1 K=1 K=1,则减少连续部分为存在两种情况:合并相邻的 2 2 2 个非负数 a i , a i + 2 a_i,a_{i+2} ai,ai+2;删除已选择的一个非负数 a j a_j aj。对于前者,贡献为非负数间的负数 a i + 1 a_{i+1} ai+1;对于后者,贡献为其负数 − a j -a_j aj。可以观察到上述两种情况不可能出现在连续的位置。那么答案为 s u m sum sum 加上所有负数以及取负后的非负数 a ′ a' a 中的最大值。

K = 2 K=2 K=2,根据贪心策略,答案一定为以下两种情况之一:选择最大值 a i ′ a'_i ai,以及除了 a i − 1 ′ , a i ′ , a i + 1 ′ a'_{i-1},a'_i,a'_{i+1} ai1,ai,ai+1 之外的最小值;选择 a i − 1 ′ , a i + 1 ′ a'_{i-1},a'_{i+1} ai1,ai+1。按照这个思路,可以推广到 K > 2 K>2 K>2 的情况。

使用一个链表以及一个大根堆维护 a ′ a' a,当 c n t > M cnt>M cnt>M 时,不断取出堆顶,设其为 a i ′ a'_i ai,将其贡献加入答案,删除 a i − 1 ′ , a i ′ , a i + 1 ′ a'_{i-1},a'_i,a'_{i+1} ai1,ai,ai+1,插入 a i − 1 ′ + a i + 1 ′ − a i ′ a'_{i-1}+a'_{i+1}-a'_i ai1+ai+1ai(相当于取消 a i ′ a'_i ai 的操作,而进行 a i − 1 ′ , a i + 1 ′ a'_{i-1},a'_{i+1} ai1,ai+1 的操作),此时涵盖了上述推论的两种情况,且保证序列保持非负数与负数交替、连续。

考虑边界情况,位于左右边界的负数部分显然不可能取到,且会影响合并连续部分时的逻辑,需要在更新链表的同时进行删除。

#include <algorithm>
#include <cstdio>
#include <queue>
using namespace std;
typedef pair<int, int> P;
const int maxn = 100005;
struct node
{
#define a(x) list[x].a
#define pre(x) list[x].pre
#define nxt(x) list[x].nxt
    int a, pre, nxt;
} list[maxn];
int N, M, A[maxn], tot, head, tail;
bool del[maxn];
priority_queue<P> Q;

void init()
{
    tot = 2, head = 1, tail = 2;
    a(head) = a(tail) = 0;
    nxt(head) = tail, pre(tail) = head;
}

int insert(int p, int a)
{
    int q = ++tot;
    a(q) = a;
    pre(nxt(p)) = q, nxt(q) = nxt(p);
    nxt(p) = q, pre(q) = p;
    return q;
}

void remove(int p)
{
    pre(nxt(p)) = pre(p), nxt(pre(p)) = nxt(p);
}

int main()
{
    scanf("%d%d", &N, &M);
    for (int i = 1; i <= N; ++i)
        scanf("%d", A + i);
    int res = 0, cnt = 0, l = 1, r = N;
    while (l <= r && A[l] < 0)
        ++l;
    while (l <= r && A[r] < 0)
        --r;
    init();
    for (int i = l, x = head; i <= r; ++i)
    {
        bool f = A[i] >= 0;
        int s = A[i];
        while (i + 1 <= r && f == (A[i + 1] >= 0))
            s += A[++i];
        if (s >= 0)
            res += s, ++cnt;
        x = insert(x, f ? -s : s);
        Q.push(P(a(x), x));
    }
    while (cnt > M)
    {
        --cnt;
        while (del[Q.top().second])
            Q.pop();
        res += Q.top().first;
        int x = Q.top().second;
        Q.pop();
        if (x == nxt(head))
        {
            del[nxt(x)] = del[x] = 1;
            remove(nxt(x)), remove(x);
        }
        else if (x == pre(tail))
        {
            del[pre(x)] = del[x] = 1;
            remove(pre(x)), remove(x);
        }
        else
        {
            a(x) = a(pre(x)) + a(nxt(x)) - a(x);
            Q.push(P(a(x), x));
            del[pre(x)] = del[nxt(x)] = 1;
            remove(pre(x)), remove(nxt(x));
        }
    }
    printf("%d\n", res);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值