bzoj 2288: 【POJ Challenge】生日礼物 贪心+优先队列

24 篇文章 0 订阅

题意

给出一个长度为n的数列,要求从中取出不超过m段连续的数,使其和最大。
n<=100000

分析

之前看黄学长的blog说可以转换成数据备份,但却没有思路,看了题解后才恍然大悟。

这题可以把一段同号的数并成一个数,那么就变成了一个正负交替的序列,然后把头尾的负数去掉。
有一种想法就是把所有的正值都加起来,然后把所有数的绝对值加进优先队列,每次去一个最小的出来然后减掉,最后的即为答案。
为什么是正确的呢?因为如果减去的值在原数列中为正则相当于不要这个数,否则就相当于选了这个负数然后把两边的正值合并起来。
但有一个问题,就是若选了一个数那么其两边的数就必定不能被选,那么就转换成了数据备份了。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#define N 100005
using namespace std;

struct data
{
    int val,pos;

    bool operator < (const data &a) const
    {
        return a.val<val;
    }
};

int n,m,a[N],b[N],last[N],next[N],vis[N];
priority_queue <data> q;

int main()
{
    freopen("2151.in","r",stdin);
    //freopen("test.out","w",stdout);
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
        scanf("%d",&b[i]);
    int n1=1;
    a[1]=b[1];
    for (int i=2;i<=n;i++)
        if (a[n1]<=0&&b[i]<=0||a[n1]>=0&&b[i]>=0) a[n1]+=b[i];
        else a[++n1]=b[i];
    if (a[n1]<=0) n1--;
    if (a[1]<=0)
    {
        for (int i=1;i<n1;i++)
            a[i]=a[i+1];
        n1--;
    }
    n=n1;
    int ans=0,sum=0;
    for (int i=1;i<=n;i++)
    {
        if (a[i]>0)
        {
            sum++;
            ans+=a[i];
        }
        data u;
        u.val=abs(a[i]);u.pos=i;
        q.push(u);
        next[i]=i+1;last[i]=i-1;
        a[i]=abs(a[i]);
    }
    next[n]=last[1]=0;
    if (sum<=m)
    {
        printf("%d",ans);
        return 0;
    }
    m=sum-m;
    for (int i=1;i<=m;i++)
    {
        data u=q.top();
        q.pop();
        while (vis[u.pos]&&!q.empty())
        {
            u=q.top();
            q.pop();
        }
        if (vis[u.pos]) break;
        ans-=u.val;
        if (q.empty()) break;
        int x=u.pos;
        if (!last[x])
        {
            vis[x]=1;vis[next[x]]=1;
            last[next[next[x]]]=0;
        }else if (!next[x])
        {
            vis[x]=1;vis[last[x]]=1;
            next[last[last[x]]]=0;
        }else
        {
            vis[next[x]]=1;vis[last[x]]=1;
            u.val=a[x]=a[next[x]]+a[last[x]]-a[x];
            if (next[next[x]]) last[next[next[x]]]=x;
            if (last[last[x]]) next[last[last[x]]]=x;
            last[x]=last[last[x]];next[x]=next[next[x]];
            q.push(u);
        }
    }
    printf("%d",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值