P2048 [NOI2010]超级钢琴(主席树+前缀合)

传送门

题目:

小Z是一个小有名气的钢琴家,最近C博士送给了小Z一架超级钢琴,小Z希望能够用这架钢琴创作出世界上最美妙的音乐。

这架超级钢琴可以弹奏出n个音符,编号为1至n。第i个音符的美妙度为Ai,其中Ai可正可负。

一个“超级和弦”由若干个编号连续的音符组成,包含的音符个数不少于L且不多于R。我们定义超级和弦的美妙度为其包含的所有音符的美妙度之和。两个超级和弦被认为是相同的,当且仅当这两个超级和弦所包含的音符集合是相同的。

小Z决定创作一首由k个超级和弦组成的乐曲,为了使得乐曲更加动听,小Z要求该乐曲由k个不同的超级和弦组成。我们定义一首乐曲的美妙度为其所包含的所有超级和弦的美妙度之和。小Z想知道他能够创作出来的乐曲美妙度最大值是多少。

输入输出格式

输入格式:

 

输入第一行包含四个正整数n, k, L, R。其中n为音符的个数,k为乐曲所包含的超级和弦个数,L和R分别是超级和弦所包含音符个数的下限和上限。

接下来n行,每行包含一个整数Ai,表示按编号从小到大每个音符的美妙度。

 

输出格式:

 

输出只有一个整数,表示乐曲美妙度的最大值。

题意:从所有区间长度大于l小于r的区间段中,选择最大的k个区间合相加,输出;

题解:每个区间合可以由前缀合差值表示,枚举左端点,可以得到sum=max(sum[i+j-1]-sum[i-1])(r>=j>=l),对于每次枚举的点,左边项是不变的,对于枚举的左端点的第k大的区间合就等于右边第k大的前缀减当前点的上一个前缀,第一次从所有区间的最大的区间合中选出最大的区间,在将相同左端点的第一个小于它的区间加入,重复操作,直到选够k个;

ac代码:

#include<stdio.h>
#include<vector>
#include<string.h>
#include<algorithm>
#include<iostream>
#include<math.h>
#include<queue>
#include<set>
#include<map>
#define ll long long
using namespace std;
const int maxn=5e5+5;
//const ll inf=9223372036854775800;
const int inf=1e9;
struct node{
    int su,p,k;
    friend bool operator < (node a,node b){
        return a.su<b.su;
    }
};
int ls[maxn*20],rs[maxn*20],num[maxn*20];
int ti[maxn],tot,ans;
int sum[maxn],sum1[maxn];
priority_queue<node> q;
void add(int &now,int lst,int l,int r,int z){
    now=++tot;
    ls[now]=ls[lst];
    rs[now]=rs[lst];
    num[now]=num[lst]+1;
    if(l==r)
        return ;
    int mid=(l+r)>>1;
    //printf("%d %d\n",mid,z);
    if(mid>=z)
        add(ls[now],ls[lst],l,mid,z);
    else
        add(rs[now],rs[lst],mid+1,r,z);
}
int query(int now,int lst,int k1,int l,int r){
    //printf("%d %d %d %d %d\n",num[now],num[lst],l,r,k1);
    //printf("%d %d %d %d %d %d\n\n",rs[now],ls[now],rs[lst],ls[lst],now,lst);
    if(l==r)
        return l;
    int mid=(l+r)>>1;
    int num1=num[rs[now]]-num[rs[lst]];
    if(num1>=k1)
        return query(rs[now],rs[lst],k1,mid+1,r);
    else
        return query(ls[now],ls[lst],k1-num1,l,mid);
}
int main( )
{
    int n,m,L,R,i,j;
    scanf("%d%d%d%d",&n,&m,&L,&R);
    ls[0]=rs[0]=num[0]=ti[0]=tot=sum[0]=0;
    for(int a=1;a<=n;a++)
        scanf("%d",&i),sum[a]=sum[a-1]+i,sum1[a]=sum[a];
        sort(sum1+1,sum1+n+1);
        int nu=unique(sum1+1,sum1+n+1)-sum1;
    for(int a=1;a<=n;a++){
        int k=lower_bound(sum1+1,sum1+nu,sum[a])-sum1;
        add(ti[a],ti[a-1],1,n,k);
    }
    for(int a=1;a<=n;a++){
        int rt=min(n,a+R-1);
        int lt=a+L-1;
        if(lt>n)
            break;
        ans=query(ti[rt],ti[lt-1],1,1,n);
        node now={sum1[ans]-sum[a-1],a,1};
        q.push(now);
    }
    ll ans1=0;
    while(!q.empty()){
        if(m==0)
        break;
        node now=q.top();
        q.pop();
        ans1+=now.su;
        m--;
        int a=now.p;
        int rt=min(n,a+R-1);
        int lt=a+L-1;
        if(lt>n||rt-lt+1<=now.k)
            continue;
        ans=query(ti[rt],ti[lt-1],now.k+1,1,n);
        node now1={sum1[ans]-sum[a-1],a,now.k+1};
        q.push(now1);
    }
    printf("%lld\n",ans1);
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值