[题解]NOI2010超级钢琴

给定一个l,r,表示只能选择起点为i,终点在i+l-1,i+r-1内的区间,每个区间的贡献为区间内的和,现在从所有合法区间内选k个使和最大

先转化成前缀和,要求的是$max(sum[k]-sum[i-1])(1 \le i \le n)(i+l-1 \le k \le i+r-1)$,可以看到sum[i-1]是定值,这样对于一个点只要sum[k]最大即可,用st表可以实现,前k大我们可以用堆取k次,

但是取完sum[k]后可能还有次大的k之类的,所以每次我们取完一个点时把它分裂成$(i,l,k-1),(i,k+1,r)$

常用处理方法,堆贪心每次取完后扩展出可能优的状态,前k大取k次

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=500009;
int n,k,L,R;
ll sum[maxn],st[maxn][21],A;
void pre(){
    for(int i=1;i<=n;i++)st[i][0]=i;
    for(int j=1;(1<<j)<=n;j++)
    for(int i=1;i+(1<<j)-1<=n;i++){
        if(sum[st[i][j-1]]>sum[st[i+(1<<(j-1))][j-1]])st[i][j]=st[i][j-1];
        else st[i][j]=st[i+(1<<(j-1))][j-1];
    }
}
int query(int l,int r){
    int k=log2(r-l+1);
    if(sum[st[l][k]]>sum[st[r-(1<<k)+1][k]])return st[l][k];
    else return st[r-(1<<k)+1][k];
}
struct node{
    int x,l,r,t;
    node(){}
    node(int xx,int Ll,int rr){
        x=xx;l=Ll;r=rr;
        t=query(l,r);
    }
    bool operator <(const node&tt)const{
        return sum[t]-sum[x-1]<sum[tt.t]-sum[tt.x-1];
    }
};
priority_queue<node>q;
int main(){
    scanf("%d%d%d%d",&n,&k,&L,&R);
    for(int i=1;i<=n;i++)scanf("%lld",&A),sum[i]=sum[i-1]+A;
    pre();
    for(int i=1;i<=n;i++)
    if(i+L-1<=n)q.push(node(i,i+L-1,min(i+R-1,n)));
    ll ans=0;
    while(k--){
        int x=q.top().x,l=q.top().l,r=q.top().r,t=q.top().t;
        q.pop();
        ans+=sum[t]-sum[x-1];
        if(l!=t)q.push(node(x,l,t-1));
        if(r!=t)q.push(node(x,t+1,r));
    }
    printf("%lld",ans);
}

 

posted @ 2019-10-10 08:10  羊肉汤泡煎饼  阅读(107)  评论(0编辑  收藏  举报