BZOJ2006 超级钢琴 [贪心][RMQ][堆]

2 篇文章 0 订阅
2 篇文章 0 订阅

当我们已经确定了所选区间的右端点,我们可以将区间和转变为前缀相减的形式,然后求[l,r]的最大值也就是求s[r]-s[l-1]的最大值,因为r确定,而l只能在一段固定的区间,我们就RMQ了。然后我们对于每个可行的右端点都找出最优的左端点,把它们扔到优先队列里一个一个取出来就行了,很容易避免删除操作,因为加入原来x的左端点可以在[a,b]中选择,我们与其从[a,b]中去掉y,不如将[a,b]分裂成[a,y-1]和[y+1,b]两段,然后将这两段都扔到优先队列中。也就是说,我们在优先队列中存放的其实是一个四元组(sum,x,a,b),分别代表区间和,右端点,合法左端点的区间最左边和最右边。然后贪心去搞就AC。

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=500005;  
int sum[maxn];  
int n,k,L,R;
long long ans;  
struct data{  
    int i,l,r,t;  
    data(){}  
    data(int _i,int _l,int _r,int _t){
        i=_i,l=_l,r=_r,t=_t;  
    }
    bool operator < (const data &rhs)const{  
        return sum[t]-sum[i-1]<sum[rhs.t]-sum[rhs.i-1];  
    }  
};  
priority_queue<data>que;  
int f[maxn][20];  
void rmq_init(){  
    for(int i=1;i<=n;i++)f[i][0]=i;  
    for(int j=1;(1<<j)<=n;j++)  
        for(int i=1;i+(1<<j)-1<=n;i++){  
            int x=f[i][j-1],y=f[i+(1<<(j-1))][j-1];  
            f[i][j]=sum[x]>sum[y]?x:y;  
        }  
}
int rmq(int l,int r){  
    int k=0;
    while((1<<(k+1))<=r-l+1)k++;
    int x=f[l][k],y=f[r-(1<<k)+1][k];  
    return sum[x]>sum[y]?x:y;  
}
void solve(){
    ans=0;
    for(register int i=1;i<=n;i++)if(i+L-1<=n){  
        int t=min(n,i+R-1);  
        que.push(data(i,i+L-1,t,rmq(i+L-1,t)));  
    }
    while(k--){  
        data cur=que.top();que.pop();  
        ans+=sum[cur.t]-sum[cur.i-1];  
        if(cur.t-1>=cur.l)que.push(data(cur.i,cur.l,cur.t-1,rmq(cur.l,cur.t-1)));  
        if(cur.t+1<=cur.r)que.push(data(cur.i,cur.t+1,cur.r,rmq(cur.t+1,cur.r)));  
    }  
}
template<class T>inline void read(T &res){
    static char ch;T flag=1;
    while((ch=getchar())<'0'||ch>'9')if(ch=='-')flag=-1;res=ch-48;
    while((ch=getchar())>='0'&&ch<='9')res=res*10+ch-48;res*=flag;
}
int main(){
    read(n),read(k);read(L),read(R);  
    for(register int i=1;i<=n;i++)read(sum[i]);  
    for(register int i=1;i<=n;i++)sum[i]+=sum[i-1];  
    rmq_init();  
    solve();  
    cout<<ans<<endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值