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;
}