BZOJ2006[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,表示按编号从小到大每个音符的美妙度。
N<=500,000
k<=500,000
-1000<=Ai<=1000,1<=L<=R<=N且保证一定存在满足条件的乐曲

输出

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

样例输入

4 3 2 3
3
2
-6
8

样例输出

11
【样例说明】
共有5种不同的超级和弦:
音符1 ~ 2,美妙度为3 + 2 = 5
音符2 ~ 3,美妙度为2 + (-6) = -4
音符3 ~ 4,美妙度为(-6) + 8 = 2
音符1 ~ 3,美妙度为3 + 2 + (-6) = -1
音符2 ~ 4,美妙度为2 + (-6) + 8 = 4
最优方案为:乐曲由和弦1,和弦3,和弦5组成,美妙度为5 + 2 + 4 = 11。
 
区间和可以转化成两个前缀和相减。
先考虑怎么找最大的区间和。
因为对于每个左端点i,能选的右端点是一段区间即[i+L-1,i+R-1]。
那么只要枚举以每个点为左端点,找到能选的右端点区间中的最大前缀和,取这些右端点和左端点(实际上是左端点前面那个点)差值的最大值就是最大的区间和。
那么选完最大值,再考虑第二大的。只要在剩下的那些以每个点为左端点的区间最大值中选最大的就好了?
显然不一定,假设刚才选的那个区间左端点为i,那么以i为左端点的能选的区间中第二大的可能比其他的那些最大值还大。
而这个第二大的相当于是删除了刚才第一大的后,以i为左端点的能选区间中第一大的。
因此我们只要每次在以每个点为左端点的右端点能选区间中找到最大值,在所有最大值中取出最大的,然后再把这个最大的删掉,下次再取剩下最大的就好了。
具体点来说就是用堆维护当前所有取出区间的最大值(堆中每个元素存区间和以及这个区间和的左端点两个信息),记录以每个点为左端点时右端点能选区间中已经取了前几大的,每次弹出堆顶元素,假设堆顶元素区间的左端点是i,而以i为左端点的右端点区间中已经取了j个,就再找到第j+1大的右端点,再把i和这个点构成的区间和插入堆中。
至于区间第k大,直接用主席树维护就好了。
注意如果能取的右端点区间长度小于当前要取的k大小时就不能取了。
#include<set>
#include<map>
#include<stack>
#include<queue>
#include<cmath>
#include<cstdio>
#include<vector>
#include<bitset>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
#define pr pair<int,int>
using namespace std;
ll ans;
int n,m;
int x,y;
int L,R;
int cnt;
int s[500010];
int t[500010];
int root[500010];
int ls[30000010];
int rs[30000010];
int sum[30000010];
priority_queue<pr>q;
void updata(int &rt,int pre,int l,int r,int k)
{
    rt=++cnt;
    sum[rt]=sum[pre]+1;
    ls[rt]=ls[pre];
    rs[rt]=rs[pre];
    if(l==r)
    {
        return ;
    }
    int mid=(l+r)>>1;
    if(k<=mid)
    {
        updata(ls[rt],ls[pre],l,mid,k);
    }
    else
    {
        updata(rs[rt],rs[pre],mid+1,r,k);
    }
}
int query(int x,int y,int l,int r,int k)
{
    if(l==r)
    {
        return l;
    }
    int res=sum[rs[y]]-sum[rs[x]];
    int mid=(l+r)>>1;
    if(res<k)
    {
        return query(ls[x],ls[y],l,mid,k-res);
    }
    else
    {
        return query(rs[x],rs[y],mid+1,r,k);
    }
}
int main()
{
    scanf("%d%d%d%d",&n,&m,&L,&R);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&x);
        s[i]=s[i-1]+x;
        updata(root[i],root[i-1],0,1000000000,s[i]+500000000);
    }
    for(int i=1;i+L-1<=n;i++)
    {
        t[i]++;
        x=query(root[i+L-2],root[min(n,i+R-1)],0,1000000000,t[i]);
        q.push(make_pair(x-s[i-1],i));
    }
    while(m)
    {
        pr miku=q.top();
        q.pop();
        m--;
        x=miku.first;
        y=miku.second;
        ans+=1ll*(x-500000000);
        if(t[y]<min(n,y+R-1)-(y+L-1)+1)
        {
            t[y]++;
            x=query(root[y+L-2],root[min(n,y+R-1)],0,1000000000,t[y]);
            q.push(make_pair(x-s[y-1],y));
        }
    }
    printf("%lld",ans);
}

转载于:https://www.cnblogs.com/Khada-Jhin/p/9726981.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值