bzoj2288 生日礼物(贪心)(堆)(链表)

题目

ftiasch 18岁生日的时候,lqp18_31给她看了一个神奇的序列 A1, A2, …, AN. 她被允许选择不超过 M 个连续的部分作为自己的生日礼物。
自然地,ftiasch想要知道选择元素之和的最大值。你能帮助她吗?

我的想法

相邻的两个数如果同为正数或负数可以合并成一个大的正数或负数,这样整个数列就成了正负交替的了。
当m=1时,最大子序列是答案。
我们考虑设置反悔机制。假设最大子序列的区间为[l,r],我们给[l,r]间的数取相反数,如果可以和旁边的数合并则合并。如果选择了反悔机制中的数,意思是取消刚刚的操作。
这个想法在复杂度上已经输了,其正确性还有待证明……

题解

堆+链表+贪心
相邻两数同号合并的想法没有错,正解的难点在与逆向思维。
假设全部的正数都选了,ans=所有正数和。这是m>=正数集合的情况。
如果不是,我们就要进行部分舍弃,使新正数集合==m。
舍弃有两种方式:
1、选择一个正数,ans-=a[i],表示不选这个正数集合。
2、选择一个负数,ans-=abs(a[i]),表示把负数两边正数连带中间的负数一起选了。
因为不能连续,所以选了a[i]这个集合,a[l[i]]和a[r[i]]就不能选了。对于正数和负数的a[i]我们以绝对值的方式来评定它的价值,越小越优。
这样问题就转化成了bzoj1150

代码

#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int inf=(1ll<<31)-1;
const int maxl=1e5+10;

int n=0,N,m;
int a[maxl];

int l[maxl],r[maxl];

struct Q
{
    int a,p;
    bool operator<(Q q1)const
    {
        return abs(a)>abs(q1.a);
    }
};
priority_queue<Q> q;

void remove(int x)
{
    a[x]=inf;
    l[r[x]]=l[x];
    r[l[x]]=r[x];
}

int main()
{
    int tot=0,ans=0;
    scanf("%d%d",&N,&m);
    for(int i=1;i<=N;i++)
    {
        int x;scanf("%d",&x);
        if(i>1 && (a[n]>=0)==(x>=0)) a[n]+=x;
        else a[++n]=x;
    }
    for(int i=1;i<=n;i++)
    {
        if(a[i]>0) tot++,ans+=a[i];
        q.push((Q){a[i],i});
        l[i]=i-1;r[i]=i+1;
    }
    
    while(tot>m)
    {
        tot--;
        while(!q.empty() && q.top().a!=a[q.top().p]) q.pop();//除去旧状态
        
        Q top=q.top();q.pop();
        int x=top.p,lx=l[x],rx=r[x];
        if(top.a<=0 && (lx==0 || rx==n+1)) tot++;//边界的负数选了没意义
        else
        {
            ans-=abs(a[x]);
            a[x]=a[x]+a[lx]+a[rx];
            q.push((Q){a[x],x});
            remove(lx);remove(rx);
        }
    }
    printf("%d\n",ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/A_Bright_CH/article/details/81637754