【NOI2010】超级钢琴

本文介绍了一种算法,用于寻找给定音符序列中不同和弦的最大美妙度总和,通过主席树和优先队列实现高效查找。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题描述

小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,表示按编号从小到大每个音符的美妙度。

输出格式

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

样例输入 1

4 3 2 3
3
2
-6
8

样例输出1

11

样例输入2

10 13 3 7
595
384
-435
-197
-677
661
4
-100
-653
220

样例输出2

1112

这里写图片描述


题解

即求前k 大连续区间和的和。
当k=1时,显然单调队列维护前缀和求解。
当k>1时,由于区间长度有限制,考虑快速查询以某个数为起点的第k 大连续区间和的方法。
求前缀和,问题变为求一段区间的第k 大前缀和。
即,对于第i 个数,以它为起点的第k 大连续区间和即为[ i+l-1,i+r-1 ]的第k 大前缀和减去第 i-1个数的前缀和。
考虑使用主席树。
下面求前k 大连续区间和。
假设现在讨论到求第x 大区间和。对于以第i 个数作为起点的区间和,只有之前未使用过的最大的区间和才可能是答案。
于是用堆。开始时把每个数为起点的最大连续区间和放入堆中,记录下值、起点位置,排名(开始时是1)。之后取k次,每次取完后将同一起点的次大的区间和放进堆内。
具体见代码。


代码

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <queue>
#define ll long long
using namespace std;
priority_queue<pair<ll ,pair<ll, ll > > > q;
const ll Q=40000000;
ll ls[Q],rs[Q],cnt[Q],n,k,a,b,d[500005],dis[500005],tot,maxr=500000,QAQ[500005];
void xiu(ll now,ll last,ll l,ll r,ll v)
{
    cnt[now]=cnt[last]+1;
    if(l==r)return;
    ls[now]=ls[last];
    rs[now]=rs[last];
    ll mid=(l+r)>>1;
    if(v<=mid)
        ls[now]=++tot,xiu(ls[now],ls[last],l,mid,v);
    else
        rs[now]=++tot,xiu(rs[now],rs[last],mid+1,r,v);
}
void bt(ll now,ll l,ll r)
{
    if(l==r)return;
    ll mid=(l+r)>>1;
    ls[now]=++tot,bt(tot,l,mid);
    rs[now]=++tot,bt(tot,mid+1,r);
}
ll find(ll l,ll r,ll v)
{
    while(l<=r)
    {
        ll mid=(l+r)>>1;
        if(d[mid]>=v)r=mid-1;
        else l=mid+1;
    }
    return l;
}
ll gr(ll nowl,ll nowr,ll l,ll r,ll v)
{
    if(cnt[nowr]-cnt[nowl]<v)return -1;
    if(l==r)return l;
    ll mid=(l+r)>>1,lls=rs[nowl],rrs=rs[nowr];
    if(cnt[rrs]-cnt[lls]>=v)return gr(lls,rrs,mid+1,r,v);
    return gr(ls[nowl],ls[nowr],l,mid,v-(cnt[rrs]-cnt[lls]));
}
int main()
{
    ll i,ans=0,ha,x,y,z;
    scanf("%lld%lld%lld%lld",&n,&k,&a,&b);
    tot=n;
    for(i=1;i<=n;i++)
        scanf("%lld",&x),QAQ[i]=d[i]=dis[i]=x+dis[i-1];
    sort(d+1,d+n+1);
    ha=unique(d+1,d+n+1)-(d+1);
    bt(0,1,maxr);
    for(i=1;i<=n;i++)
    {
        dis[i]=find(1,ha,dis[i]);
        xiu(i,i-1,1,maxr,dis[i]);
    }
    for(i=1;i+a-1<=n;i++)
    {
        ha=gr(i+a-2,min(n,i+b-1),1,maxr,1);
        q.push(make_pair(d[ha]-QAQ[i-1],make_pair(i,1)));
    }
    for(i=1;i<=k;i++)
    {
        x=q.top().first;
        y=q.top().second.first;
        z=q.top().second.second;
        q.pop();
        ans+=x;
        ha=gr(y+a-2,min(n,y+b-1),1,maxr,z+1);
        if(ha!=-1)q.push(make_pair(d[ha]-QAQ[y-1],make_pair(y,z+1)));
    }
    printf("%lld",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值