题意
给出一个长度为n的数列,要求从中取出不超过m段连续的数,使其和最大。
n<=100000
分析
之前看黄学长的blog说可以转换成数据备份,但却没有思路,看了题解后才恍然大悟。
这题可以把一段同号的数并成一个数,那么就变成了一个正负交替的序列,然后把头尾的负数去掉。
有一种想法就是把所有的正值都加起来,然后把所有数的绝对值加进优先队列,每次去一个最小的出来然后减掉,最后的即为答案。
为什么是正确的呢?因为如果减去的值在原数列中为正则相当于不要这个数,否则就相当于选了这个负数然后把两边的正值合并起来。
但有一个问题,就是若选了一个数那么其两边的数就必定不能被选,那么就转换成了数据备份了。
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#define N 100005
using namespace std;
struct data
{
int val,pos;
bool operator < (const data &a) const
{
return a.val<val;
}
};
int n,m,a[N],b[N],last[N],next[N],vis[N];
priority_queue <data> q;
int main()
{
freopen("2151.in","r",stdin);
//freopen("test.out","w",stdout);
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
scanf("%d",&b[i]);
int n1=1;
a[1]=b[1];
for (int i=2;i<=n;i++)
if (a[n1]<=0&&b[i]<=0||a[n1]>=0&&b[i]>=0) a[n1]+=b[i];
else a[++n1]=b[i];
if (a[n1]<=0) n1--;
if (a[1]<=0)
{
for (int i=1;i<n1;i++)
a[i]=a[i+1];
n1--;
}
n=n1;
int ans=0,sum=0;
for (int i=1;i<=n;i++)
{
if (a[i]>0)
{
sum++;
ans+=a[i];
}
data u;
u.val=abs(a[i]);u.pos=i;
q.push(u);
next[i]=i+1;last[i]=i-1;
a[i]=abs(a[i]);
}
next[n]=last[1]=0;
if (sum<=m)
{
printf("%d",ans);
return 0;
}
m=sum-m;
for (int i=1;i<=m;i++)
{
data u=q.top();
q.pop();
while (vis[u.pos]&&!q.empty())
{
u=q.top();
q.pop();
}
if (vis[u.pos]) break;
ans-=u.val;
if (q.empty()) break;
int x=u.pos;
if (!last[x])
{
vis[x]=1;vis[next[x]]=1;
last[next[next[x]]]=0;
}else if (!next[x])
{
vis[x]=1;vis[last[x]]=1;
next[last[last[x]]]=0;
}else
{
vis[next[x]]=1;vis[last[x]]=1;
u.val=a[x]=a[next[x]]+a[last[x]]-a[x];
if (next[next[x]]) last[next[next[x]]]=x;
if (last[last[x]]) next[last[last[x]]]=x;
last[x]=last[last[x]];next[x]=next[next[x]];
q.push(u);
}
}
printf("%d",ans);
return 0;
}