题解 | #超级钢琴#
超级钢琴
https://ac.nowcoder.com/acm/problem/17626
ST 表 + 优先队列 + 前缀和
题意
给一个长度 为 n 的序列,让你从中选 k 个长度在 [L,R] 范围内的区间 (同一个区间不可选多次) 要求:这 k 个区间的区间和 相加 得到的值 应该最大
思路
(1):求出前缀和 (2):枚举 i 令其作为区间左边界,则右边界ri可取值 [i+L-1,i+R-1] 使用RMQ在区间[i+L-1,i+R-1]里找到pos => 目标区间里最大值的下标,即:对于左端点 i 有最大区间和 sum[pos] - sum[i-1] 同时,我们把 {i:此时的左边界,i+L-1:右边界最小值,i+R-1:右边界最大值,最大区间和} 作为一个结构体放到优先队列里面 重复执行上述过程,直到 i 枚举结束 (3) 弹出最大值 优先队列 每次会弹出一个 最大区间和 最大的结构体,该结构里含的最大区间和 对答案有贡献 假设此时弹出的结构体为{id,l,r,sum[pos]-sum[id-1]} 即:对于左端点id 其右端点取 pos 时,对答案有贡献 我们不立刻把它舍弃,因为对于左端点 id,其他合法的右端点对答案可能也会有贡献 所以我们在弹出它后,还要把{id,l,pos-1,maxm1}、{id,pos+1,r,maxm2} 给入队,来更新下一个最大值 综上:Code 如下
Code
#include <bits/stdc++.h> using namespace std; const int N = 5e5+10; typedef long long ll; int lg[N]; int sum[N]; int f[N][20]; int n,k,L,R; struct node{ int id; int l,r; int maxm; bool operator < (const node & x) const{ return maxm < x.maxm; } }; void ST(){ lg[1]=0; lg[2]=1; for(int i=3;i<=n;i++) lg[i]=lg[i/2]+1; int len=lg[n]; for(int j=1;j<=len;j++) for(int i=1;i-1+(1<<j)<=n;i++) { int x=f[i][j-1],y=f[i+(1<<(j-1))][j-1]; f[i][j]=sum[x]>sum[y]?x:y; } } int query(int l,int r){ int len=lg[r-l+1]; int x=f[l][len],y=f[r+1-(1<<len)][len]; return sum[x]>sum[y]?x:y; } int main(){ cin>>n>>k>>L>>R; for(int i=1;i<=n;i++) cin>>sum[i],sum[i]+=sum[i-1],f[i][0]=i; ST(); priority_queue<node>q; for(int i=1;i<=n;i++) { if(i+L-1>n) break; if(i+L-1==n) q.push({i,n,n,sum[n]-sum[i-1]}); else if(n>i+L-1) { int x=query(i+L-1,min(n,i+R-1)); q.push({i,i+L-1,min(n,i+R-1),sum[x]-sum[i-1]}); } } ll ans=0; while(k--){ auto t=q.top(); q.pop(); int id=t.id,l=t.l,r=t.r,maxm=t.maxm; ans+=maxm; int index=query(l,r); if(index-1>=l){ int x=query(l,index-1); q.push({id,l,index-1,sum[x]-sum[id-1]}); } if(index+1<=r){ int x=query(index+1,r); q.push({id,index+1,r,sum[x]-sum[id-1]}); } } cout<<ans<<endl; return 0; }