BZOJ2006 [NOI2010]超级钢琴(划分树+堆)

【题解】

(划分树做法)

要求出长度为l~r的前k大连续和,可以转化为k次求第i大连续和,这与划分树的作用有关 

先把前缀和预处理出来。一段连续和为 S(j)-S(i),固定i之后为 S(i+len)-S(i-1) (i为起点,len为长度,len∈[l,r])
这里面,若i一定,len就在固定区间内变动,连续和的k大值对应S(i+len)的k大值,问题就转化为"求给定区间的k大值",就可以用划分树了 
但是起点有多个,而题目中"k次求第i大连续和"没办法真的快速做到一次求一个 
可以先求出对于每个起点的区间内最大连续和来预备着,把它们存到堆里,每次询问时取出堆顶并放入这个起点对应的次大和即可 

因此题目转化为:求 S(i+l-1)-s(i-1),S(i+l)-s(i-1),…,S(i+r-1)-s(i-1) (i∈[1,n-l+1])中前k大的值 
Ⅰ首先,建一个大根堆。对于每个i,求出max{ S(i+l-1),S(i+l),…,S(i+r-1) }-s(i-1)加入堆中 
Ⅱ然后,对于k次询问,每次取出堆顶,看这个堆顶是由哪个i得到的,再把这个i对应的{S(i+l-1),S(i+l),…,S(i+r-1)}-s(i-1)的次大值加入堆 

时间复杂度:划分树预处理_O( n*log(n) ) +Ⅰ_O( n*log(n) ) +Ⅱ_O( k*log(n) )


【代码】

#include<stdio.h>
#include<stdlib.h>
typedef long long LL;
struct node
{
    int v[500005],num[500005];
};
node T[20];
int s[500005]={0},p[500005]={0},heap[500005]={0},pos[500005]={0};
int ph=0;
int min(int a,int b)
{
    if(a<b) return a;
    return b;
}
void jh(int* a,int* b)
{
    int t=*a;
    *a=*b;
    *b=t;
}
void jh_heap(int a,int b)
{
    jh(&heap[a],&heap[b]);
    jh(&pos[a],&pos[b]);
}
void kp(int low,int high)
{
    int i=low,j=high,mid=s[(i+j)/2];
    while(i<j)
    {
        while(s[i]<mid) i++;
        while(s[j]>mid) j--;
        if(i<=j)
        {
            jh(&s[i],&s[j]);
            i++;
            j--;
        }
    }
    if(j>low) kp(low,j);
    if(i<high) kp(i,high);
}
void build(int left,int right,int deep)
{
    int mid=(left+right)/2,ln=left,rn=mid+1,isame=mid-left+1,same=0,i;
    if(left==right) return;
    for(i=left;i<=right;i++)
        if(T[deep].v[i]<s[mid]) isame--;
    for(i=left;i<=right;i++)
    {
        if(i==left) T[deep].num[i]=0;
        else T[deep].num[i]=T[deep].num[i-1];
        if(T[deep].v[i]<s[mid])
        {
            T[deep+1].v[ln++]=T[deep].v[i];
            T[deep].num[i]++;
        }
        if(T[deep].v[i]>s[mid]) T[deep+1].v[rn++]=T[deep].v[i];
        if(T[deep].v[i]==s[mid])
        {
            same++;
            if(isame>=same)
            {
                T[deep+1].v[ln++]=T[deep].v[i];
                T[deep].num[i]++;
            }
            else T[deep+1].v[rn++]=T[deep].v[i];
        }
    }
    build(left,mid,deep+1);
    build(mid+1,right,deep+1);
}
int cx(int x,int y,int k,int left,int right,int deep)
{
    int mid=(left+right)/2,before,have,b,h,tx,ty,fk=y-x+1+1-k;
    if(left==right) return T[deep].v[left];
    if(x==left)
    {
        before=0;
        have=T[deep].num[y];
    }
    else
    {
        before=T[deep].num[x-1];
        have=T[deep].num[y]-T[deep].num[x-1];
    }
    if(fk<=have)//左 
    {
        tx=left+before;
        ty=left+before+have-1;
        k=ty-tx+1+1-fk;
        return cx(tx,ty,k,left,mid,deep+1);
    }
    else//右 
    {
        b=x-1-left-before+1;
        h=y-x+1-have;
        tx=mid+1+b;
        ty=mid+1+b+h-1;
        k=ty-tx+1+1-(fk-have);
        return cx(tx,ty,k,mid+1,right,deep+1);
    }
}
void tj(int x,int t)
{
    int i;
    heap[++ph]=t;
    pos[ph]=x;
    for(i=ph;i!=1;i/=2)
    {
        if(heap[i]>heap[i/2]) jh_heap(i,i/2);
        else return;
    }
}
void sc()
{
    int i=1;
    jh_heap(1,ph);
    ph--;
    while(i*2<=ph)
    {
        i*=2;
        if(i+1<=ph&&heap[i+1]>heap[i]) i++;
        if(heap[i]>heap[i/2]) jh_heap(i,i/2);
        else return;
    }
}
int main()
{
    LL ans=0;
    int n,k,l,r,i,x;
    scanf("%d%d%d%d",&n,&k,&l,&r);
    for(i=1;i<=n;i++)
    {
        scanf("%d",&s[i]);
        s[i]+=s[i-1];
        T[0].v[i]=s[i];
    }
    T[0].v[0]=0;
    kp(1,n);
    build(1,n,0);
    for(i=1;i<=n-l+1;i++)
    {
        p[i]=1;
        tj(i,cx(i+l-1,min(n,i+r-1),1,1,n,0)-T[0].v[i-1]);
    }
    for(i=1;i<=k;i++)
    {
        ans+=(LL)heap[1];
        x=pos[1];
        sc();
        p[x]++;
        if((x+l-1)+p[x]-1<=min(n,x+r-1)) tj(x,cx(x+l-1,min(n,x+r-1),p[x],1,n,0)-T[0].v[x-1]);
    }
    printf("%lld",ans);
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值