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