洛谷P2048 [NOI2010]超级钢琴 (优先队列+RMQ)
题目链接
https://www.luogu.org/problem/P2048
题意
给出n个音符的美妙度,“超级和旋”由若干个编号连续的音符组成,包含的音符个数不少于L其不多于R,我们定义超级和弦的美妙度为其包含的所有音符的美妙度之和,求由k个超级和旋组成的乐曲的美妙度之和最大值是多少
思路
输出最大的k个sum[r]-sum[l-1] (L<=r-l+1<=R)之和
当右端点固定不变时,左端点的前缀和越小越好
固定右端点r后,左端点的被限制在了区间[r-R,r-L]内
RMQ查出在这段左端点区间内,左端点前缀和的最小值的点
把所有的这些放到一个大根堆里
取出一个元素后
若原区间[a,b] 左端点选的位置是p
那么原区间分裂为两个区间[a,p-1] 和 [p+1,b]
即 若原来区间的右端点是End,左端点可选区间为[a,b]
那么当[a,b]内选位置p当左端点,且作为前k大用过后,
右端点为End的区间可选左端点变成了 [a,p-1],[p+1,b],放入堆中即可
#include<bits/stdc++.h>
using namespace std;
const int maxx = 5e5+10;
typedef long long LL;
struct node
{
int l,r;
int ed,pos,val;
bool operator < (const node &t)const
{
return val<t.val;
}
};
int sum[maxx];
int mi[maxx][20];
priority_queue<node>q;
int getmin(int l,int r)
{
int k=log2(r-l+1);
return sum[mi[l][k]]<sum[mi[r-(1<<k)+1][k]]?mi[l][k]:mi[r-(1<<k)+1][k];
}
int main()
{
int n,k,L,R;
scanf("%d%d%d%d",&n,&k,&L,&R);
for(int i=1;i<=n;i++)
{
scanf("%d",&sum[i]);
sum[i]+=sum[i-1];
mi[i][0]=i;
}
//rmq维护区间最小值
for(int j=1;1<<j<=n;j++)
for(int i=0;i+(1<<j)-1<=n;i++) //注意这道题要把0也算进去
if(sum[mi[i][j-1]]<sum[mi[i+(1<<(j-1))][j-1]])mi[i][j]=mi[i][j-1];
else mi[i][j]=mi[i+(1<<(j-1))][j-1];
node tmp,res;
for(int i=1;i<=n;i++) //固定右端点,寻找最小的左端点的前缀和
{
tmp.ed=i;
tmp.l=max(i-R,0);
tmp.r=i-L;
if(tmp.l>tmp.r)continue;
tmp.pos=getmin(tmp.l,tmp.r);
tmp.val=sum[i]-sum[tmp.pos];
q.push(tmp);
}
LL ans=0;
while(k--)
{
tmp=q.top();
q.pop();
res.ed=tmp.ed;
ans+=tmp.val;
if(tmp.l!=tmp.pos)
{
res.pos=getmin(tmp.l,tmp.pos-1);
res.l=tmp.l;
res.r=tmp.pos-1;
res.val=sum[res.ed]-sum[res.pos];
q.push(res);
}
if(tmp.r!=tmp.pos)
{
res.pos=getmin(tmp.pos+1,tmp.r);
res.l=tmp.pos+1;
res.r=tmp.r;
res.val=sum[res.ed]-sum[res.pos];
q.push(res);
}
}
printf("%lld\n",ans);
return 0;
}