【链表】生日礼物

本文介绍了一个有趣的问题:从一个序列中最多选择m段,求这些段的和的最大值。通过预处理同符号段并使用小根堆进行动态调整,确保在减少选择段数时损失最小。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意
给出一个长度为n的序列,最多选择m段,求能选的最大值。
【思路】
首先我们把同符号的一段累加成一个值。
然后我们选择所有的正数,如果选的个数比m大,就每次减少选的段数,直到数值等于m。
我们将所有的值按绝对值排序(丢进小根堆里),每次挑选出最小的一个。为什么?因为我们要使减少段数后的损失最小。
对于正数,我们将答案减去它,相当于不选这个;
对于负数,我们将答案减去它的绝对值,为什么?因为同符号累加成一段,所以就是(正数 当前绝对值最小的负数 正数),我们相当于选择本来不选的这个负数使相邻正数连起来,段数减小1,答案减少的也是这个数的绝对值。
因此无论这个数是正数还是负数,我们只要选择它,答案的损失就是其绝对值。每次选最小的一个,这样答案损失的最小。
然后我们不选(正数)/选(负数)这个数以后,它与它左边右边的段就相当于连了起来。因此我们要将这个新段丢回堆里,删掉堆里它左边右边的段,以后可能再次被选上这个段。代码中删除实际上是用标记代替,如果当前堆顶被标记了说明这个点实际上应该被删除,就直接踢出这个点。
【代码】

#include<bits/stdc++.h>
#define MAXN 110000
#define mp make_pair
#define pa pair<int,int>
#define inf 0x3f3f3f3f
#define LL long long
#define jcp dalao
using namespace std;
int read()
{
    int ans=0,f=1,x;
    while(1)
    {
        x=getchar();
        if(x=='-')f=-1;
        if(x>='0'&&x<='9')break;
    }
    while(x>='0'&&x<='9')
    {
        ans=ans*10+x-'0';
        x=getchar();
    }
    return ans*f;
}

struct cmp
{
    bool operator()(pa a,pa b)
    {
        return abs(a.first)>abs(b.first);
    }
};

priority_queue< pa, vector <pa> , cmp > q;

int n,m,tot=0,sum=0,ans=0,a[MAXN],pre[MAXN],nex[MAXN];
bool mark[MAXN];

void del(int x)
{
    mark[x]=true;
    pre[nex[x]]=pre[x];
    nex[pre[x]]=nex[x];
}
int main()
{

    n=read();m=read();tot=1;

    for(int i=1;i<=n;i++)
    {
        int x=read();
        if((LL)a[tot]*x>=0)a[tot]+=x;
        else a[++tot]=x;
    }

    n=tot;

    for(int i=1;i<=tot;i++)if(a[i]>0)sum++,ans+=a[i];

    for(int i=1;i<=n;i++)
    {
        nex[i]=i+1;
        pre[i]=i-1;
        q.push( mp( a[i] , i ) );
    }

    memset(mark,false,sizeof(false));

    while(sum>m)
    {
        sum--;

        while(mark[q.top().second])q.pop();

        int x=q.top().second,l=pre[x],r=nex[x];
        q.pop();

        if(pre[x]!=0&&nex[x]!=n+1)ans-=abs(a[x]);
        else
        {
            if (a[x]>0)ans-=a[x];
            else
            {
                sum++;
                continue;
            }
        }

        a[x]=a[pre[x]]+a[nex[x]]+a[x];

        del(pre[x]);del(nex[x]);

        q.push(mp(a[x],x));
    }

    printf("%d\n",ans);

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值