题面
有一数列,包含 个数,编号为 至 。第 个数权值为 。
我们要找到 段连续子序列,满足其中数的数量 保证 ,求这 段序列中所有数字的权值和的最大值。
所有数据满足:,, ,且保证一定有解。
题解
可以想到,求区间和用前缀和。使用贪心思想来解决这个问题。
设 表示以 为左端点,右端点在 区间中,序列的最大值。设 是前缀和。
则 。其中 ,使 最大。
那么就要求 。由于 值固定,也就是求 ,也就是求区间最大值(前缀和数组),可以用 st 表解决。
求得对于所有 ,满足使 的值最大的 后,把这些相关值(,,,,)搞成结构体,放入一个堆中。
循环 次,每次取最大的 ,把 累加起来。
取完后,可能还有次优 值存在于 。故也要求使 和 的值最大的对应 值,把求解过程中的相关值也放入堆(即在区间 排除了 $x_i $)。
累加起来的值即为答案。
代码
#include<bits/stdc++.h>
using namespace std;
namespace _yz
{
const int N=600000+5,log=19;
struct music{int s,l,r,a;};//采用四元组,表示i,l,r,xi。fi(l,r) 现场求即可。
int n,k,L,R,b[N],st[25][N];
long long ans=0,a[N];
priority_queue<music>q;
bool operator<(music x,music y){return a[x.a]-a[x.s-1]<a[y.a]-a[y.s-1];}
int query(int l,int r)
{
int len=r-l+1;
if(a[st[b[len]][l]]>a[st[b[len]][r+1-(1<<b[len])]])
return st[b[len]][l];
return st[b[len]][r+1-(1<<b[len])];
}
void yzmain()
{
scanf("%d%d%d%d",&n,&k,&L,&R);
for(int i=1;i<=n;i++) scanf("%lld",a+i),a[i]+=a[i-1];
for(int i=1;i<=n;i++) st[0][i]=i;//st 表存的是 xi
for(int i=1;i<=log;i++) b[1<<i]=1;
for(int i=1;i<=n;i++) b[i]+=b[i-1];
for(int i=1;i<=log;i++)
for(int j=1;j<=n;j++)
if(a[st[i-1][j]]>a[st[i-1][j+(1<<(i-1))]])
st[i][j]=st[i-1][j];
else
st[i][j]=st[i-1][j+(1<<(i-1))];
for(int i=1;i<=n;i++)
if(i+L-1<=n) q.push((music){i,i+L-1,min(i+R-1,n),query(i+L-1,min(i+R-1,n))});//注意不要超出 n
for(int i=1;i<=k;i++)
{
music now=q.top();q.pop();
ans+=a[now.a]-a[now.s-1];//计算 fi(l,r)
if(now.a!=now.l) q.push((music){now.s,now.l,now.a-1,query(now.l,now.a-1)});
if(now.a!=now.r) q.push((music){now.s,now.a+1,now.r,query(now.a+1,now.r)});
}
printf("%lld\n",ans);
return;
}
}
int main()
{
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
_yz::yzmain();
return 0;
}
本文作者:Yurchiu
本文链接:https://yz-hs.github.io/1a6156b5845c/
版权声明:本博客中所有原创文章除特别声明外,均允许规范转载,转载请注明出处。所有非原创文章,按照原作者要求转载。