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

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

输入输出样例

输入样例#1:
4 3 2 3
3
2
-6
8
输出样例#1:
11
说明

共有5种不同的超级和弦:

  1. 音符1 ~ 2,美妙度为3 + 2 = 5
  2. 音符2 ~ 3,美妙度为2 + (-6) = -4
  3. 音符3 ~ 4,美妙度为(-6) + 8 = 2
  4. 音符1 ~ 3,美妙度为3 + 2 + (-6) = -1
  5. 音符2 ~ 4,美妙度为2 + (-6) + 8 = 4

最优方案为:乐曲由和弦1,和弦3,和弦5组成,美妙度为5 + 2 + 4 = 11。
这里写图片描述
所有数据满足:-1000 ≤ Ai ≤ 1000,1 ≤ L ≤ R ≤ n且保证一定存在满足要求的乐曲。

分析:
我们可以先跑一个前缀和,考虑每一个位置开始的,长度为 [L,R] [ L , R ] 的串的最大值。
一个串 [l,r] [ l , r ] 可以表示成 sum[r]sum[l1] s u m [ r ] − s u m [ l − 1 ] ,起点一定,显然相当于求最大值,然后丢进一个堆里。
当我们选了一个堆顶 [l,r] [ l , r ] 串,并统计了答案后,假设这个串是以 l l 开头的第k大的,我们把这个东西从以 l l 开头的第k+1大的串插入,如果没有就不插入。第 k k <script type="math/tex" id="MathJax-Element-626">k</script>大直接维护主席树即可。

代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <queue>
#include <algorithm>
#define LL long long

const int maxn=5e5+7;

using namespace std;

int n,m,l,r,cnt;
int root[maxn],b[maxn],a[maxn];
LL ans;

struct node{
    int l,r,data,sum;
}t[maxn*25];

struct rec{
    int x,st,k;
};

bool operator <(rec a,rec b)
{
    return a.x<b.x;
}

priority_queue <rec> q;

void ins(int &p,int q,int l,int r,int x)
{
    if (!p) p=++cnt;
    t[p].data=t[q].data+1;
    if (l==r) return;
    int mid=(l+r)/2;
    if (x<=mid) t[p].r=t[q].r,ins(t[p].l,t[q].l,l,mid,x);
           else t[p].l=t[q].l,ins(t[p].r,t[q].r,mid+1,r,x);
}

int getrank(int p,int q,int l,int r,int k)
{
    if (l==r) return l;
    int mid=(l+r)/2;
    int sum=t[t[p].r].data-t[t[q].r].data;
    if (sum>=k) return getrank(t[p].r,t[q].r,mid+1,r,k);
           else return getrank(t[p].l,t[q].l,l,mid,k-sum);
}

int main()
{
    scanf("%d%d%d%d",&n,&m,&l,&r);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);   
    for (int i=1;i<=n;i++)
    {
        a[i]+=a[i-1];
        b[i]=a[i];
    }
    sort(b+1,b+n+1);
    int size=unique(b+1,b+n+1)-b-1;
    for (int i=1;i<=n;i++) a[i]=lower_bound(b+1,b+size+1,a[i])-b;
    for (int i=1;i<=n;i++) ins(root[i],root[i-1],1,n,a[i]); 
    for (int i=1;i<=n;i++)
    {
        int L=i+l-1,R=min(i+r-1,n);
        if (L>n) continue;
        int x=b[getrank(root[R],root[L-1],1,n,1)]-b[a[i-1]];
        q.push((rec){x,i,1});
    }       
    for (int i=1;i<=m;i++)
    {
        rec c=q.top();
        q.pop();
        ans+=(LL)c.x;
        int L=c.st+l-1,R=min(c.st+r-1,n);
        if ((L<=n) && (R-L+1>c.k))
        {
            int x=b[getrank(root[R],root[L-1],1,n,c.k+1)]-b[a[c.st-1]];
            q.push((rec){x,c.st,c.k+1});
        }
    }
    printf("%lld",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值