Description
Input
Output
Sample Input
4 3 2 3
3
2
-6
8
Sample Output
11
Hint
共有5种不同的超级和弦:音符1 ~ 2,美妙度为3 + 2 = 5
音符2 ~ 3,美妙度为2 + (-6) = -4
音符3 ~ 4,美妙度为(-6) + 8 = 2
音符1 ~ 3,美妙度为3 + 2 + (-6) = -1
音符2 ~ 4,美妙度为2 + (-6) + 8 = 4
最优方案为:乐曲由和弦1,和弦3,和弦5组成,美妙度为5 + 2 + 4 = 11。
n,k<=500,000
题目大意
求长度在[L,R]之间的最大的K个子序列的和
题解
考虑每个左端点$i$,合法的区间的右端点会在$[i+L,i+R]$内。
不妨枚举所有左端点,找到以其为左端点,满足题意的最大子序列。
用贪心的思想,显然这些序列中最大的一定是满足题意的,统计后将该序列删除。
但若删除,就意味着以i为左端的序列都被排除,显然会流失掉一些有用的值。
原来的区间$[i+L,i+R]$,假设在$maxi$处取得最大值,我们不妨将其裂解成两个区间$[i+L,maxi-1]$,$[maxi+1,i+R]$并分别找出在这两个小区间内的最大值,将他们加入待选序列中。
显然维护就直接用堆,堆中记录一个5元组$(v,i,l,r,w)$分别表示该子序列的值$v$,左端点的位置$i$,右端点的区间$[l,r]$和去最值的右端点的位置$w$,以$v$为关键字,建大根堆。
最后一个问题就是查找了,我们不妨预处理出前缀和。已知$i~j$的序列的值为$sum[j]-sum[i-1]$,既然左端点固定,那么只要找右端点处的$sum$最大值即可。用$RMQ$实现查找区间最大值。
#include<map>
#include<cmath>
#include<ctime>
#include<queue>
#include<stack>
#include<cstdio>
#include<string>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define LL long long
#define RE register
#define IL inline
using namespace std;
const int N=; IL int Max(int a,int b){return a>b ? a:b;}
IL int Min(int a,int b){return a<b ? a:b;} struct node
{
int v,i,l,r,w; }t,tmp;
bool operator < (const node &a,const node& b)//重载,建立大根堆
{
return a.v<b.v;
}
priority_queue<node>Q; int n,k,l,r,op;
LL ans;
int f[N+][],where[N+][]; int maxn,maxi;
IL void RMQ(int l,int r)//查询最值
{
int opt=log2(r-l+);
if (f[l][opt]>=f[r-(<<opt)+][opt]) maxn=f[l][opt],maxi=where[l][opt];
else maxn=f[r-(<<opt)+][opt],maxi=where[r-(<<opt)+][opt];
} int main()
{
memset(f,,sizeof(f));
f[][]=;
scanf("%d%d%d%d",&n,&k,&l,&r);
op=log2(n);
for (int i=;i<=n;i++)
{
scanf("%d",&f[i][]);
where[i][]=i;
f[i][]+=f[i-][];
}
for (int t=;t<=op;t++)
for (int i=;i<=n;i++) if (i+(<<(t-))->n) break;
else//倍增,where记录取最值的位置
{
if (f[i][t-]>=f[i+(<<(t-))][t-]) f[i][t]=f[i][t-],where[i][t]=where[i][t-];
else f[i][t]=f[i+(<<(t-))][t-],where[i][t]=where[i+(<<(t-))][t-];
}
for (int i=;i<=n-l+;i++)
{
RMQ(i+l-,i+Min(r-,n-i));
t.v=maxn-f[i-][],t.i=i,t.l=i+l-,t.r=i+Min(r-,n-i),t.w=maxi;
Q.push(t);
}
while (k--)
{
t=Q.top();
Q.pop();
ans+=t.v;
if (t.w>t.l)//要判断裂解的区间是否合法
{
RMQ(t.l,t.w-);
tmp.v=maxn-f[t.i-][],tmp.i=t.i,tmp.l=t.l,tmp.r=t.w-,tmp.w=maxi;
Q.push(tmp);
}
if (t.w<t.r)
{
RMQ(t.w+,t.r);
tmp.v=maxn-f[t.i-][],tmp.i=t.i,tmp.l=t.w+,tmp.r=t.r,tmp.w=maxi;
Q.push(tmp);
}
}
printf("%lld\n",ans);
return ;
}