BZOJ 2006 [NOI2010]超级钢琴

题目在这里呀~

题意

好题好题
求k个区间使得和最大,要求区间的长度在L到R之间。

题解

非常有意思的一道题
考虑前缀和,那么以l为左端点的区间的和都是是s[i]-s[l-1]。
那么用RMQ来预处理出区间s[]的最大值。
可以想到取前k大的话,那应该是要用堆的。
堆中记录一个五元组(i,l,r,val,pos)表示左端点为i,右端点在[l,r]之间,最大价值为val,最大价值所对应的右端点为pos。
取出一个五元组(i,l,r,val,pos),将它分裂成两半,(i,l,pos-1,val’,pos’),(i,pos+1,r,val”,pos”)。
最后取k个就可以了(写得有些匆忙,如有疑问欢迎提出qwq)

//Suplex
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#define N 500500
using namespace std;
int n,k,L,R,a[N],s[N],f[N][23];
long long ans;

struct leo{
    int i,l,r,val,pos;
    bool operator < (const leo & A) const {return val<A.val;}
};

priority_queue<leo>heap;

int MAX(int a,int b)
{
    return s[a]>s[b] ? a : b;
}

int main()
{
    scanf("%d%d%d%d",&n,&k,&L,&R);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]),s[i]=s[i-1]+a[i];

    for(int i=1;i<=n;i++) f[i][0]=i;
    for(int j=1;j<=(int)log2(n);j++)
        for(int i=1;i<=n-(1<<j)+1;i++)
            f[i][j]=MAX(f[i][j-1],f[i+(1<<(j-1))][j-1]);

    for(int i=1;i<=n;i++)
        if(i+L-1<=n){
            int ls=i+L-1,rs=min(i+R-1,n),len=log2(rs-ls+1);
            int pos=MAX(f[ls][len],f[rs-(1<<len)+1][len]);
            int val=s[pos]-s[i-1];
            heap.push((leo){i,ls,rs,val,pos});
        }else break;

    while(!heap.empty() && k){
        leo now=heap.top();heap.pop();
        ans+=now.val;k--;
        leo ls=now,rs=now;
        ls.r=now.pos-1;rs.l=now.pos+1;
        if(ls.r>=ls.l){
            int len=log2(ls.r-ls.l+1);
            ls.pos=MAX(f[ls.l][len],f[ls.r-(1<<len)+1][len]);
            heap.push((leo){now.i,ls.l,ls.r,s[ls.pos]-s[now.i-1],ls.pos});
        }
        if(rs.r>=rs.l){
            int len=log2(rs.r-rs.l+1);
            rs.pos=MAX(f[rs.l][len],f[rs.r-(1<<len)+1][len]);
            heap.push((leo){now.i,rs.l,rs.r,s[rs.pos]-s[now.i-1],rs.pos});
        }
    }
    printf("%lld\n",ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/leo_nasir/article/details/80955483