Description
ftiasch 18岁生日的时候,lqp18_31给她看了一个神奇的序列 A1, A2, …, AN. 她被允许选择不超过 M
个连续的部分作为自己的生日礼物。自然地,ftiasch想要知道选择元素之和的最大值。你能帮助她吗?
Input
第1行,两个整数 N (1 ≤ N ≤ 105) 和 M (0 ≤ M ≤ 105), 序列的长度和可以选择的部分。
第2行, N 个整数 A1, A2, …, AN (0 ≤ |Ai| ≤ 104), 序列。
Output
一个整数,最大的和。
Sample Input
5 2
2 -3 2 -1 2
Sample Output
5
题解
这题其实有点类似种树/数据备份那题
首先连续的一段正或者连续的一段负的缩成一个点,权为这一段的总和。这样这个序列就变成了正负相间的序列
如果正的点数目<=m时,我们不需要做任何处理,直接输出这些点的总和即可
如果不是,将所有点的绝对值加入堆,用一个链表记录这个点前后的是什么。我们每次取出绝对值最小的点,将他与他两边的点合并形成新点。对于每一次进行这样的操作,我们会发现正的点数一定会少1。如果取出的是负点,则代表取了这个负点两边的正点与负点的集合形成的一个新段。如果取出的是正点,则代表删除了这个点不再取。
需要注意的是负点的边界,因为如果一个负点在边界的时候我们再取他与两边合并是没有用的,特判一下
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
typedef long long LL;
struct node
{
int pos,c;
friend bool operator <(node n1,node n2){return abs(n1.c)>abs(n2.c);}
};
priority_queue<node> q;
int n,m;
int a[110000];
int s[110000],ln;
int nxt[110000],pre[110000];
bool vis[110000];
void del(int x)
{
vis[x]=false;
pre[nxt[x]]=pre[x];
nxt[pre[x]]=nxt[x];
}
int main()
{
// freopen("a.in","r",stdin);
//freopen("a.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
int sum=0,tt=0,ln=1;
for(int i=1;i<=n;i++)
{
if((LL)s[ln]*a[i]>=0)s[ln]+=a[i];
else s[++ln]+=a[i];
}
int ans=0;
for(int i=1;i<=ln;i++)if(s[i]>0)ans+=s[i],sum++;
for(int i=1;i<=ln;i++)pre[i]=i-1,nxt[i]=i+1;
//pre[1]=nxt[ln]=-1;
node tmp;
for(int i=1;i<=ln;i++)
{
tmp.c=s[i];tmp.pos=i;
q.push(tmp);
}
memset(vis,true,sizeof(vis));
while(sum>m)
{
sum--;
tmp=q.top();
while(!vis[tmp.pos])q.pop(),tmp=q.top();
q.pop();
if(pre[tmp.pos]!=0 && nxt[tmp.pos]!=ln+1)ans-=abs(s[tmp.pos]);
else if(s[tmp.pos]>0) ans-=s[tmp.pos];
else {sum++;continue;}
s[tmp.pos]=s[tmp.pos]+s[pre[tmp.pos]]+s[nxt[tmp.pos]];
del(pre[tmp.pos]);del(nxt[tmp.pos]);
tmp.c=s[tmp.pos];
q.push(tmp);
}
printf("%d\n",ans);
return 0;
}

本文介绍了一种求解特定条件下最大子序列和的问题解决算法。该算法通过处理正负相间序列,并采用堆和链表等数据结构,实现高效求解在限制条件下序列中可选部分的最大和。
356

被折叠的 条评论
为什么被折叠?



