《算法竞赛进阶指南》生日礼物

翰翰 1818 岁生日的时候,达达给她看了一个神奇的序列 A1,A2,…,ANA1,A2,…,AN。

她被允许从中选择不超过 MM 个连续的部分作为自己的生日礼物。

翰翰想要知道选择元素之和的最大值。

你能帮助她吗?

输入格式

第一行包含两个整数 N,MN,M。

第二行包含 NN 个整数 A1∼ANA1∼AN。

输出格式

输出一个整数,表示答案。

数据范围

1≤N,M≤1051≤N,M≤105,
|Ai|≤104|Ai|≤104

输入样例:

5 2 
2 -3 2 -1 2

输出样例:

5

解题思路:

 

将所有的正数和负数分开,即如图,绿色是整数,红色是负数;
共有两种选法
{
    假设res为所有正数端的和, cnt为共选择了多少个正数段
    
    1:每次选择所有的绿色,假设选择了cnt个所有的正数段(则k = cnt - m,
    因为最多选择m个, k即为要从中减去的绿色的数量),
    因此最终答案为即为res -= a[i]因为a[i] > 0所以可以写成res -= abs(a[i]), 
    因此题目转化为选出k尽量小的数,才能使得结果尽可能增大;
    
    2:如图不能选择图中数字1的方法,因为要使得结果最大,所以只能选择数字2,不能选择图中的数字1,
    因为1中包含负数要选负数的话只能和他后面的正数一起选择
    若选择数字2,则我们可以将图中数字2中的3条边合为一条边;即权值为res -= abs(x);
}
所以题目最后就转化为了这题的解法:https://blog.csdn.net/qq_61935738/article/details/125703897?spm=1001.2014.3001.5501 

代码:

#include <cmath>
#include <cstdio>
#include <vector>
#include <iostream>

using namespace std;

const int N = 100010;

int a[N];//数组a表示将序列变为正数数和负数分开的数组
int n, m;
bool st[N];//判断小根堆里的数是否已经用过
int l[N], r[N];//双链表的左右儿子;

void removes(int p)
{
    r[l[p]] = r[p];//删除双链表中的左右儿子
    l[r[p]] = l[p];
    
    st[p] = true;//删除堆里的左右儿子
}

int main()
{
    cin >> n >> m;
    
    int k = 1;
    for (int i = 1; i <= n; i ++ )
    {
        int x;
        scanf("%d", &x);
        
        if (!x) continue;//遇到0跳过,加不加0答案都一样;
        
        if (a[k] * x < 0) a[++ k] = x;//将正数,负数分开
        else a[k] += x;
    }
    
    n = k;
    int cnt = 0, res = 0;
    for (i = 1; i <= n; i ++ )//记录所有大于0的数字的数目,最后用k = cnt - m,即最后只需要每次循环k次
    {
        if (a[i] > 0) 
        {
            cnt ++ ;
            res += a[i];//最后答案:进行k次循环,假设每次取出的最小值为s[k],则最后的答案为res - s[k];
        }
    }
    
    for (int i = 1; i <= n; i ++ )//建立双链表
    {
        l[i] = i - 1, r[i] = i + 1;
        
        heap.push({abs(a[i])}, i);
    }
    
    while (cnt > m)//循环k次
    {
        while (st[heap.top().second]) heap.pop();//st表示已经被删掉的元素
        
        auto t = heap.top()//每次取出最小值
        
        int v = t.first, p = t.second;
        
        if (l[p] != 0 && r[p] != n + 1 || a[p] > 0)//若a[p] < 0且在边界,则不能删除,就像图中数字一的情况
        {
            cnt -- ;
            res -= v;
            
            int left = l[p], right = r[p];
            a[p] += a[left] + a[right];
            
            heap.push({abs(a[p]), p});
            
            removes(left);//删除左右儿子节点,将左右儿子节点和p节点融合为一个新节点
            remover(right);
        }
    }
    
    cout << res << endl;
    
    return ;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

啥也不会hh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值