『结题记录』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 表最值位置的方式

posted @ 2023-10-14 10:30  tkt  阅读(3)  评论(0编辑  收藏  举报