bzoj2006 noi2010 超级钢琴 主席树 + 优先队列

Time Limit: 20 Sec  Memory Limit: 552 MB
Submit: 2435  Solved: 1195

Description

小 Z是一个小有名气的钢琴家,最近C博士送给了小Z一架超级钢琴,小Z希望能够用这架钢琴创作出世界上最美妙的音乐。 这架超级钢琴可以弹奏出n个音符,编号为1至n。第i个音符的美妙度为Ai,其中Ai可正可负。 一个“超级和弦”由若干个编号连续的音符组成,包含的音符个数不少于L且不多于R。我们定义超级和弦的美妙度为其包含的所有音符的美妙度之和。两个超级和 弦被认为是相同的,当且仅当这两个超级和弦所包含的音符集合是相同的。 小Z决定创作一首由k个超级和弦组成的乐曲,为了使得乐曲更加动听,小Z要求该乐曲由k个不同的超级和弦组成。我们定义一首乐曲的美妙度为其所包含的所有 超级和弦的美妙度之和。小Z想知道他能够创作出来的乐曲美妙度最大值是多少。

Input

第一行包含四个正整数n, k, L, R。其中n为音符的个数,k为乐曲所包含的超级和弦个数,L和R分别是超级和弦所包含音符个数的下限和上限。 接下来n行,每行包含一个整数Ai,表示按编号从小到大每个音符的美妙度。

Output

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

Sample Input

4 3 2 3
3
2
-6
8

Sample Output

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。

HINT

N<=500,000


k<=500,000


-1000<=Ai<=1000,1<=L<=R<=N且保证一定存在满足条件的乐曲
 
 
思路:
每一个子序列的权值和可以转化为两个前缀和之差。我们考虑以每一个位置为结尾的子序列,它的权值和可以看作是以该位置为结尾的前缀和减去它前面的某个前缀和。

那么想要这个子序列的权值和尽量大,那么就要前面的那个前缀和尽可能小。如果数目不够,就第2小。再不够,就第3小。

于是我们维护一个优先队列,维护3元组(v,r,k),v表示这个序列的权值和,r表示这个序列的结尾位置,k表示v是r结尾的符合条件的序列的第k大权值和

以每个位置为结尾的最大子序列权值和,每次取出堆顶时再放进堆中一个结尾位置相同的第k+1大的权值和。

这样k次就能出解。

这要求我们能够快速求出区间第k小,利用可持久化线段树即可。

时间复杂度O(klogn).

 

 

代码:

                                            
  //File Name: bzoj2006.cpp
  //Author: long
  //Mail: 736726758@qq.com
  //Created Time: 2016年07月31日 星期日 14时12分42秒
                                   
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <queue>

#define LL long long
#define hash _hash_

using namespace std;

const int MAXN = 500000 + 3;

int h_num[MAXN],hash[MAXN],tot,top;

void hash_init(){
    sort(h_num+1,h_num+top+1);
    tot = 0;
    hash[++tot] = h_num[1];
    for(int i=2;i<=top;i++){
        if(h_num[i] != h_num[i-1])
            hash[++tot] = h_num[i];
    }
}

int hash_find(int x){
    int l = 1,r = tot,mid;
    while(l <= r){
        mid = (l + r) >> 1;
        if(hash[mid] < x) l = mid + 1;
        else r = mid - 1;
    }
    return l;
}

struct T{
    int v,r,k;
    bool operator<(const T&a) const{
        return v < a.v;
    }
};
priority_queue<T> que;
struct Node{
    int ls,rs,w;
    Node(){ls = rs = w = 0;}
}node[MAXN * 20];
int a[MAXN],sum[MAXN],root[MAXN],sz,N,K,L,R;

void update(int &i,int l,int r,int w){
    node[++sz] = node[i];
    i = sz;
    node[i].w++;
    if(l == r) return ;
    int mid = (l + r) >> 1;
    if(w <= mid) update(node[i].ls,l,mid,w);
    else update(node[i].rs,mid+1,r,w);
}

int query(int i,int j,int l,int r,int k){
    if(l == r) return l;
    int t = node[node[j].ls].w - node[node[i].ls].w;
    int mid = (l + r) >> 1;
    if(t >= k) return query(node[i].ls,node[j].ls,l,mid,k);
    else return query(node[i].rs,node[j].rs,mid+1,r,k-t);
}

void get(int i,int &l,int &r){
    l = i - R, r = i - L;
    if(l < 0) l = 0;
    if(r < 0) r = -1;
}

LL solve(){
    sum[0] = 0;
    top = 0;
    h_num[++top] = 0;
    for(int i=1;i<=N;i++){
        sum[i] = sum[i-1] + a[i];
        h_num[++top] = sum[i];
    }
    hash_init();
    for(int i=0;i<=N;i++)
        sum[i] = hash_find(sum[i]);
    sz = 0;
    root[1] = root[0] = 0;
    update(root[1],1,tot,sum[0]);
    for(int i=1;i<=N;i++){
        root[i+1] = root[i];
        update(root[i+1],1,tot,sum[i]);
    }
    while(!que.empty()) que.pop();
    LL ans = 0;
    int now,l,r;
    for(int i=L;i<=N;i++){
        get(i,l,r);
        if(r < 0) continue;
        //printf("i = %d l = %d r = %d\n",i,l,r);
        now = hash[sum[i]] - hash[query(root[l],root[r+1],1,tot,1)];
        //printf("q = %d now = %d\n",query(root[l],root[r+1],1,tot,1),now);
        que.push((T){now,i,1});
    }
    T u; 
    while(true){
        u = que.top();
        que.pop();
        //printf("u.v = %d\n",u.v);
        ans += u.v;
        K--;
        if(!K) break;
        get(u.r,l,r);
        if(r - l + 1 <= u.k) continue;
        now = hash[sum[u.r]] - hash[query(root[l],root[r+1],1,tot,u.k+1)];
        que.push((T){now,u.r,u.k+1});
    }
    return ans;
}

int main(){
    while(~scanf("%d %d %d %d",&N,&K,&L,&R)){
        for(int i=1;i<=N;i++) scanf("%d",&a[i]);
        //cout << solve() << endl;
        printf("%lld\n",solve());
    }
    return 0;
}

 

 

 

 

 

 
 

转载于:https://www.cnblogs.com/-maybe/p/5723309.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值