bzoj2006: [NOI2010]超级钢琴(主席树+优先队列)

bzoj2006: [NOI2010]超级钢琴

bzoj2006: 超级钢琴

思路

对所有前缀和建权值线段树,先将所有右端点对应的最大的左端点丢进优先队列里,每次将优先队列队首的区间取出来后,将rk[r]++,从这个右端点对应的主席树区间中找到排名第rk[r]的值丢进优先队列,相当于用主席树求区间第k小。

代码

#include 
using namespace std;
#define dd(x) cout<<#x<<"="< vi;
typedef pair  pi;
int n,k,L,R, cnt;
ll a[maxn], s[maxn];
int T[maxn], rk[maxn];
vector v;
struct tr{
    int sum,l,r;
}t[maxn<<5];
struct node {
    ll s;
    int t;
    node (){}
    node(ll _s,int _t){
        s=_s, t=_t;
    }
    bool operator < (const node& a) const{
        return s q;
void update(int pre,int &now,int l,int r,int pos){
    now = ++ cnt;
    t[now].sum = t[pre].sum+1;
    if(l==r) return ;
    t[now].l=t[pre].l;
    t[now].r=t[pre].r;
    int mid = l+r>>1;
    if(pos<=mid) update(t[pre].l,t[now].l,l,mid,pos);
    else update(t[pre].r,t[now].r,mid+1,r,pos);
}
int qr(int x,int y,int l,int r,int k){
    if(l==r) return l;
    int mid = l+r>>1;
    int sl = t[t[y].l].sum - t[t[x].l].sum;
    if(sl>=k) return qr(t[x].l,t[y].l,l,mid,k);
    else return qr(t[x].r,t[y].r,mid+1,r,k-sl);
}
int main(){
    scanf("%d%d%d%d",&n,&k,&L,&R);
    rep(i,1,n+1) cin>>a[i], s[i]=s[i-1]+a[i],v.pb(s[i]);
    v.pb(0);
    sort(all(v));
    v.erase(unique(all(v)),v.end());
    int m= sz(v);
    update(T[0],T[1],1,m,lower_bound(all(v),0)-v.begin()+1);
    rep(i,2,n+2){
        update(T[i-1],T[i],1,m,lower_bound(all(v),s[i-1])-v.begin()+1);
        int l=max(1,i-R), r=i-L;
        if(r<=0) continue;
        int ss = qr(T[l-1],T[r],1,m,1);
        q.push(node(s[i-1]-v[ss-1],i));
        rk[i-1]=1;
    }
    ll ans = 0;
    while(k--){
        ans += q.top().s;
        int x=q.top().t;
        q.pop();
        int l=max(1,x-R), r=x-L, ss;
        ++rk[x-1];
        if(rk[x-1]>t[T[r]].sum-t[T[l-1]].sum) continue;
        ss = qr(T[l-1],T[r],1,m,rk[x-1]);
        q.push(node(s[x-1]-v[ss-1],x));
    }
    printf("%lld",ans); 
    return 0;
}

你可能感兴趣的:(bzoj2006: [NOI2010]超级钢琴(主席树+优先队列))