『结题记录』P2048 超级钢琴
RMQ 好题,但是思路贺的(悲)。思路挺好,怕忘了,写个题解罢,因为是贺的就不发博客园了。
\(Description\)
有 \(n\) 个音符,每个音符的美妙度为 \(a_i\) , 求出 \(k\) 个长度在 \([l,r]\) 之间的连续的音符称之为和弦,使 \(k\) 个和弦的美妙度最大。
\(Solution\)
因为和弦是连续,考虑使用前缀和。
先思考求最大的和弦。暴力求每个区间和然后取最大?显然会 T 飞。可以考虑确定区间的起点 \(b\) ,用 \(b+l-1\) 到 \(b+r-1\) 的最大前缀和 求出以 \(b\) 为起点的最大和弦。设 \(t\) 为 \(b+l-1\) 到 \(b+r-1\) 的最大前缀和的位置,即 \(t=\max\limits_{b+l-1\le i \le b+r-1}sum_i\) 。最大和弦即为 \(\max\limits_{1\le b \le n-l+1}sum[t]-sum[b-1]\)。需要注意 \(b+r-1>n\)的情况。
再考虑求 \(k\) 个和弦使美妙度最大。可以发现,最大美妙度可能包含若干个区间有重叠的和弦,所以每个和弦可以分裂成小和弦。考虑将每个和弦的 \(b,l,r,t\) 记录在优先队列中,取 \(k\) 次队头,队头出队,再将队头和弦可以分裂的小和弦入队。因为需要用到 \(t\) 所以要用两个 st 表, \(maxn\) 存最大前缀和, \(p\) 存最大前缀和的位置。 \(p\) 随 \(maxn\) 变化即可。
\(code\)
#include <iostream>
#include <queue>
#include <cmath>
using namespace std;
const int Max = 5e5+10;
int n,k,l,r;long long ans;
int h[Max];
struct Num{
int b,l,r,t;
bool operator <(Num i)const{
return h[t]-h[b-1] < h[i.t]-h[i.b-1];
}
};
priority_queue<Num>q;
struct ST{
int maxn[19][Max],p[19][Max];
inline void build(){
for(int j = 1;j <= 19;j++){
for(int i = 1;i+(1<<j)-1<=n;i++){
if(maxn[j-1][i]>maxn[j-1][i+(1<<j-1)]){
maxn[j][i]=maxn[j-1][i];
p[j][i]=p[j-1][i];
}
else{
maxn[j][i]=maxn[j-1][i+(1<<j-1)];
p[j][i]=p[j-1][i+(1<<j-1)];
}
}
}
}
inline int findT(int l,int r){ //找到b+l-1到b+r-1的最大前缀和的位置(t)
int k=__lg(r-l+1);
if(maxn[k][l]>maxn[k][r-(1<<k)+1]){
return p[k][l];
}
else return p[k][r-(1<<k)+1];
}
}st;
inline int read(){
int num=0,fl=1;char c=getchar();
while(c<'0'||c>'9'){
if(c=='-') fl=-1;
c=getchar();
}
while(c >='0'&&c <='9'){
num=(num<<3)+(num<<1)+(c^48);
c=getchar();
}
return num*fl;
}
signed main(){
n=read(),k=read(),l=read(),r=read();
for(int i = 1,a;i <= n;i++) a=read(),h[i]=h[i-1]+a,st.maxn[0][i]=h[i],st.p[0][i]=i;
st.build();
for(int i = 1;i+l-1 <= n;i++){
Num maxn;
maxn.t=st.findT(i+l-1,min(i+r-1,n));
maxn.b=i,maxn.l=i+l-1,maxn.r=min(i+r-1,n);
q.push(maxn);
}
while(k--){
Num to=q.top();
ans+=h[to.t]-h[to.b-1];
q.pop();
Num x;
if(to.l < to.t){//分裂
x.b=to.b;x.l=to.l;x.r=to.t-1;
x.t=st.findT(to.l,to.t-1);
q.push(x);
}
if(to.t < to.r){
x.b=to.b;x.l=to.t+1;x.r=to.r;
x.t=st.findT(to.t+1,to.r);
q.push(x);
}
}
printf("%lld",ans);
return 0;
}
\(harvest\)
学会了处理区间和的新方式
学会了记录 st 表最值位置的方式