【BZOJ】2006: [NOI2010]超级钢琴

【题意】给定长度为n的整数序列,求长度为[L,R]的前k大区间和的和。n,k<=500000。

【算法】堆+贪心+RMQ

【题解】考虑暴力是取所有长度为[L,R]的子串的前k大求和,复杂度O(n^2)。

发现左端点相同的区间[l,r]中,最大的区间和就是最大的sum[r](sum是前缀和数组)。

然后将所以左端点放进堆中,每次取出堆顶之后把第二大的r扔进去,重复k次。

现在的问题是取区间最大的r,第二大的r……区间第k大?用主席树维护即可,复杂度O(n log n)。

还可以用ST表(RMQ)维护,定义三元组(x,l,r)表示取左端点为x,右端点为[l,r]的区间最大sum[r]。

假设对于(x,l,r),取出元素y,就在堆中加入两个三元组(x,l,y-1)和(x,y+1,r)。

复杂度O(n log n)。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int maxn=500010;
int n,k,L,R,d[maxn][22],sum[maxn],p[maxn][20],logs[maxn];
struct cyc{
    int x,l,r,y;
    bool operator < (const cyc &a) const
    {return sum[y]-sum[x-1]<sum[a.y]-sum[a.x-1];}
}qp;
priority_queue<cyc>q;
void RMQ_init()
{
    logs[0]=-1;for(int i=1;i<=n;i++)logs[i]=logs[i>>1]+1;
    for(int i=1;i<=n;i++){d[i][0]=sum[i];p[i][0]=i;}
    for(int j=1;(1<<j)<=n;j++)
    for(int i=1;i+(1<<j)-1<=n;i++)
    {
        d[i][j]=max(d[i][j-1],d[i+(1<<(j-1))][j-1]);
        p[i][j]=d[i][j-1]>d[i+(1<<(j-1))][j-1]?p[i][j-1]:p[i+(1<<(j-1))][j-1];
    }
}
int RMQ(int l,int r)
{
    int K=logs[r-l+1];
    return d[l][K]>d[r-(1<<K)+1][K]?p[l][K]:p[r-(1<<K)+1][K];
}
int main()
{
    scanf("%d%d%d%d",&n,&k,&L,&R);
    sum[0]=0;
    for(int i=1;i<=n;i++){scanf("%d",&sum[i]);sum[i]+=sum[i-1];}
    RMQ_init();
    for(int i=1;i<=n;i++){
        if(i+L-1<=n)q.push((cyc){i,i+L-1,min(n,i+R-1),RMQ(i+L-1,min(n,i+R-1))});
    }
    long long ans=0;
    for(int i=1;i<=k;i++)
    {
        qp=q.top();q.pop();
        ans+=sum[qp.y]-sum[qp.x-1];
        if(qp.y>qp.l)q.push((cyc){qp.x,qp.l,qp.y-1,RMQ(qp.l,qp.y-1)});
        if(qp.y<qp.r)q.push((cyc){qp.x,qp.y+1,qp.r,RMQ(qp.y+1,qp.r)});
    }
    printf("%lld",ans);
    return 0;
}
View Code

 

posted @ 2017-08-04 12:01  ONION_CYC  阅读(188)  评论(0编辑  收藏  举报