【bzoj2006】NOI2010超级钢琴

补了下前置技能……

题意就是求一段区间的权值和前k大的子序列的和。

把段扔进优先队列

每次拿出来之后按照所选择的j进行分裂

#include<bits/stdc++.h>
#define N 500005
#define mp(a,b,c,d) (seg){a,b,c,d}
using namespace std;
typedef long long ll;
struct seg{int i,l,r,t;};
int bin[20],Log[N],n,k,l,r,a[N],st[20][N];
ll ans=0;
void initrmq(){
    Log[0]=-1;for(int i=1;i<=n;i++)Log[i]=Log[i>>1]+1;
    for(int i=1;i<=n;i++)st[0][i]=i;
    for(int i=n;i;i--)for(int j=1;j<=18;j++)
    if(i+bin[j]-1<=n){
        int t1=st[j-1][i],t2=st[j-1][i+bin[j-1]];
        st[j][i]=a[t1]>a[t2]?t1:t2;
    }
    else break;
}
inline int query(int l,int r){
    if(l==r)return l;int t=Log[r-l+1];
    int t1=st[t][l],t2=st[t][r-bin[t]+1];
    return a[t1]>a[t2]?t1:t2;
}
inline bool operator <(seg x,seg y){
    return a[x.t]-a[x.i-1]<a[y.t]-a[y.i-1];
}
inline int read(){
    int f=1,x=0;char ch;
    do{ch=getchar();if(ch=='-')f=-1;}while(ch<'0'||ch>'9');
    do{x=x*10+ch-'0';ch=getchar();}while(ch>='0'&&ch<='9');
    return f*x;
}
int main(){
    bin[0]=1;for(int i=1;i<=20;i++)bin[i]=bin[i-1]<<1;
    n=read();k=read();l=read();r=read();
    for(int i=1;i<=n;i++)a[i]=read()+a[i-1];
    initrmq();
    priority_queue<seg,vector<seg> >q;
    for(int i=1;i<=n;i++)if(i+l-1<=n){
        int t=min(n,i+r-1);q.push(mp(i,i+l-1,t,query(i+l-1,t)));
    }
    for(int i=1;i<=k;i++){
        seg t=q.top();q.pop();
        ans+=a[t.t]-a[t.i-1];
        if(t.t-1>=t.l)q.push(mp(t.i,t.l,t.t-1,query(t.l,t.t-1)));
        if(t.t+1<=t.r)q.push(mp(t.i,t.t+1,t.r,query(t.t+1,t.r)));
    }
    cout<<ans<<endl;
}

 

posted @ 2017-06-16 20:51  zcysky  阅读(214)  评论(0编辑  收藏  举报