[NOI2010]超级钢琴 倍增

[NOI2010]超级钢琴 倍增

题面

暴力:枚举区间丢入堆(O(n^2logn))

正解:考虑每次枚举和弦起点(s),那么以(s)为起点的和弦为(sum[t]-sum[s](s+L-1le tle s+R-1)),要使其最大则让(sum[t])最大,问题转换为求区间([l,r])最大(sum[i]),使用st表维护即可。

然后我们从堆中取(k)次,每次取出堆顶后,将剩下可取的区间重新丢回堆中。

#include <cstdio>
#include <queue>
#include <cmath>
#include <algorithm>
#define MAXN 500003
#define ll long long
int lg2[MAXN];
ll sum[MAXN];
int mx[MAXN][20]; // [i, i+2^j-1]
namespace rmq{
    int query(int l, int r){
        int t=lg2[r-l+1];
        int a=mx[l][t],b=mx[r-(1<<t)+1][t];
        if(sum[a]>sum[b]) return a;
        else return b;
    }
}
struct nod{
    int s,l,r,ans;
    nod(int s, int l, int r):s(s),l(l),r(r),ans(rmq::query(l, r)){}
    bool operator < (const nod &a) const{
        return sum[ans]-sum[s-1] < sum[a.ans]-sum[a.s-1];
    }
};
int n,k,L,R;
std::priority_queue <nod> q;
int main(){
    scanf("%d %d %d %d", &n, &k, &L, &R);
    for(int i=1;i<=n;++i)
        scanf("%lld", &sum[i]),sum[i]+=sum[i-1];
    for(int i=2;i<=n;++i)
        lg2[i]=lg2[i>>1]+1;
    for(register int i=1;i<=n;++i) mx[i][0]=i;
    for(register int j=1;j<=lg2[n];++j){
        int len=(1<<j);
        for(register int i=1;i+len-1<=n;++i){
            int a=mx[i][j-1],b=mx[i+(1<<(j-1))][j-1];
            if(sum[a]>sum[b]) mx[i][j]=a;
            else mx[i][j]=b;
        }
    }
    for(register int i=1;i+L-1<=n;++i)
        q.push(nod(i, i+L-1, std::min(n, i+R-1)));
    ll ans=0;
    while(k--){
        nod cur=q.top();
        q.pop();
        ans+=sum[cur.ans]-sum[cur.s-1];
        if(cur.l<=cur.ans-1) q.push(nod(cur.s, cur.l, cur.ans-1));
        if(cur.ans+1<=cur.r) q.push(nod(cur.s, cur.ans+1, cur.r));
    }
    printf("%lld", ans);
    return 0;
}
原文地址:https://www.cnblogs.com/santiego/p/11664091.html