51nod 最大M子段和V2【贪心】【链表】【堆】

16 篇文章 0 订阅
2 篇文章 0 订阅

Description

个整数组成的序列a[1],a[2],a[3],…,a[n],将这N个数划分为互不相交的M个子段,并且这M个子段的和是最大的。如果M >= N个数中正数的个数,那么输出所有正数的和。

例如:-2 11 -4 13 -5 6 -2,分为2段,11 -4 13一段,6一段,和为26。

题解

考虑贪心。我肯定是尽量把所有正数都取来,但是,这样有可能段数超过了M,所以我们要采取一些方案来减少段数,同时使得代价最小。我们可以有两种操作,一种是把某一段直接抛弃,另一种是取一段负数,使得两个正段合成一个负段。每次用堆求最小代价,同时将取来的这一段去掉,将它修改为自己加上两边的和,重新放入堆里,维护段的相邻关系用链表。同时,还要注意,当负段出现在边界时是不能取的。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define maxn 50006
#define LL long long
using namespace std;
inline char nc(){
    static char buf[100000],*i=buf,*j=buf;
    return i==j&&(j=(i=buf)+fread(buf,1,100000,stdin),i==j)?EOF:*i++;
}
inline int _read(){
    char ch=nc();int sum=0,p=1;
    while(ch!='-'&&!(ch>='0'&&ch<='9'))ch=nc();
    if(ch=='-')p=-1,ch=nc();
    while(ch>='0'&&ch<='9')sum=sum*10+ch-48,ch=nc();
    return sum*p;
}
LL _abs(LL x){return x>0?x:-x;}
struct data{
    int p;LL x;
    bool operator <(const data&b)const{return _abs(x)>_abs(b.x);}
};
priority_queue <data> heap;
int n,nn,m,tem,a[maxn],lst[maxn],nxt[maxn];
LL ans,b[maxn];
void del(int x){
    lst[nxt[x]]=lst[x];
    nxt[lst[x]]=nxt[x];
}
int main(){
    freopen("maxsum.in","r",stdin);
    freopen("maxsum.out","w",stdout);
    nn=_read();m=_read();
    for(int i=1;i<=nn;i++)a[i]=_read();
    int i=1;
    while(i<=nn){
        int j=i;n++;
        while(j<=nn&&((a[i]<=0&&a[j]<=0)||(a[i]>0&&a[j]>0)))b[n]+=a[j++];
        i=j;
    }
    for(int i=1;i<=n;i++)if(b[i]>0)ans+=b[i],tem++;
    nxt[0]=1;lst[n+1]=n;nxt[n+1]=n+1;
    for(int i=1;i<=n;i++){
        lst[i]=i-1;nxt[i]=i+1;
        data x;
        x.x=b[i];x.p=i;
        heap.push(x);
    }
    while(tem>m){
        data x=heap.top();heap.pop();
        if(lst[nxt[x.p]]!=x.p||nxt[lst[x.p]]!=x.p)continue;
        if(x.x<=0&&(!lst[x.p]||nxt[x.p]>n))continue;
        ans-=_abs(x.x);
        b[x.p]+=b[lst[x.p]]+b[nxt[x.p]];
        if(lst[x.p]!=0)del(lst[x.p]);
        if(nxt[x.p]!=n+1)del(nxt[x.p]);
        x.x=b[x.p];
        heap.push(x);tem--;
    }
    printf("%lld\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值