[BZOJ2006][NOI2010]超级钢琴

25 篇文章 0 订阅

原题地址

题目大意:有n个数,从中找出权值和前k大的不相同的长度在[L,R]之间的连续子序列(1<=n,k<=500000).

写一下自己想到的做法.
设前缀和为s[i],则所求连续子序列可以表示为以下若干行:
-s[0]+s[L],-s[0]+s[L+1],…,-s[0]+s[R]
-s[1]+s[L+1],-s[1]+s[L+2],…,-s[1]+s[R+1]

-s[i]+s[L+i+1],-s[i]+s[L+i+2],…,-s[i]+s[R+i]

然后转化成经典问题:n个递增序列,从每个序列中选择一个数然后求和,求前k大的和(要求选法不同),这个用堆可以解决.
把上面的每行看做一个序列,序列不递增,所以我们还需要求区间k大.

所以这题堆+可持久化线段树就能A掉了(代码有些无法直视囧).

AC code:

#include <cstdio>
#include <queue>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=500010;
const int LLIM=-1000000000;
const int RLIM=-LLIM;
int n,k,l,r,cnt;
int s[N],rk[N];
ll  ans;

struct Data{
    int x,v;

    Data(int x,int v):x(x),v(v) {}

    friend bool operator<(Data a,Data b){
        return a.v<b.v;
    }
};
priority_queue<Data> Q;

struct Tnod{
    int  le,ri,tot;
    Tnod *lc,*rc;
}pool[N*35];

struct Segtree{
    Tnod *root[N];

    Segtree(){
        for(int i=1;i<=n;i++) insert(&root[i],root[i-1],s[i],LLIM,RLIM);
    }

    void insert(Tnod **p,Tnod *q,int val,int L,int R){
        *p=&pool[cnt++];(*p)->le=L;(*p)->ri=R;
        if(L==R){
            if(q==NULL) (*p)->tot=1;
            else (*p)->tot=q->tot+1;
            (*p)->lc=(*p)->rc=NULL;
            return ;
        }
        int M=(L+R)>>1;
        if(val<=M){
            if(q!=NULL){
                insert(&(*p)->lc,q->lc,val,L,M);
                (*p)->rc=q->rc;
            }
            else{
                insert(&(*p)->lc,NULL,val,L,M);
                (*p)->rc=NULL;
            }
        }
        else{
            if(q!=NULL){
                insert(&(*p)->rc,q->rc,val,M+1,R);
                (*p)->lc=q->lc;
            }
            else{
                insert(&(*p)->rc,NULL,val,M+1,R);
                (*p)->lc=NULL;
            }
        }
        if((*p)->lc==NULL) (*p)->tot=(*p)->rc->tot;
        else if((*p)->rc==NULL) (*p)->tot=(*p)->lc->tot;
        else (*p)->tot=(*p)->lc->tot+(*p)->rc->tot;
    }
    int kth(int L,int R,int rank){
        return findkth(root[L-1],root[R],rank);
    }
    int findkth(Tnod *q,Tnod *p,int rank){
        if(p->le==p->ri) return p->le;
        if(p->lc==NULL){
            if(q!=NULL) return findkth(q->rc,p->rc,rank);
            else return findkth(NULL,p->rc,rank);
        }
        int ltot=p->lc->tot;
        if(q!=NULL&&q->lc!=NULL) ltot-=q->lc->tot;
        if(ltot>=rank){
            if(q!=NULL) return findkth(q->lc,p->lc,rank);
            else return findkth(NULL,p->lc,rank);
        }
        else{
            if(q!=NULL) return findkth(q->rc,p->rc,rank-ltot);
            else return findkth(NULL,p->rc,rank-ltot);
        }
    }
};

int main(){
    scanf("%d%d%d%d",&n,&k,&l,&r);
    for(int i=1;i<=n;i++){
        int t;
        scanf("%d",&t);
        s[i]=s[i-1]+t;
    }
    for(int i=0;i<=n-l;i++) rk[i]=2;
    Segtree T;
    for(int i=0;i<=n-l;i++){
        int L=i+l,R=min(i+r,n);
        Q.push(Data(i,-s[i]+T.kth(L,R,R-L+1)));
    }
    for(int i=1;i<=k;i++){
        Data d=Q.top();
        Q.pop();
        ans+=d.v;
        if(rk[d.x]<=min(r-l+1,n-d.x-l+1)){
            int L=d.x+l,R=min(d.x+r,n);
            Q.push(Data(d.x,-s[d.x]+T.kth(L,R,R-L-rk[d.x]+2)));
            rk[d.x]++;
        }
    }
    printf("%lld\n",ans);

    return 0;  
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值