BZOJ 2006 [NOI2010]超级钢琴(ST表+堆)

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

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。
 
题解:考虑每一个作为起点的,满足条件下的最优解的终点,记录前缀和,RMQ问题,记录起点终点,和查询的范围 l-r,对于以i为起点的最优解,可能的它的次优解也会成为下一次的最优解,用个大根堆维护一下,每次取出一个后,把它的次优解也推到堆里去,注意边界,取出k个即可。
 
#include <bits/stdc++.h>
#define IO_read ios::sync_with_stdio(false);cin.tie(0)
#define fre freopen("in.txt", "r", stdin)
#define _for(i,a,b) for(int i=a; i< b; i++)
#define _rep(i,a,b) for(int i=a; i<=b; i++)
#define lowbit(a) ((a)&-(a))
using namespace std;
typedef long long ll;
template <class T>
void read(T &x)
{
    char c; bool op=0;
    while(c=getchar(), c<'0'||c>'9') if(c=='-') op=1;
    x=c-'0';
    while(c=getchar(), c>='0'&&c<='9') x=x*10+c-'0';
    if(op) x=-x;
}
template <class T>
void write(T x)
{
    if(x<0) putchar('-'), x=-x;
    if(x>=10) write(x/10);
    putchar('0'+x%10);
}

const int maxn=5e5+5;
int lg[maxn], st[maxn][20], sum[maxn];

void make_st(int n)  //维护的是对应的下标
{
    lg[0]=-1;
    for(int i=1; i<=n; i++) lg[i]=lg[i>>1]+1;
    for(int i=1; i<=n; i++) st[i][0]=i;
    for(int j=1; (1<<j)<=n; j++)
        for(int i=1; i+(1<<j)-1<=n; i++) //注意这里是-1 <= n
            st[i][j]=sum[st[i][j-1]]>sum[st[i+(1<<j-1)][j-1]]? st[i][j-1]:st[i+(1<<j-1)][j-1];
}

int query(int l, int r)
{
    int k=lg[r-l+1];
    return sum[st[l][k]]>sum[st[r-(1<<k)+1][k]]? st[l][k]:st[r-(1<<k)+1][k]; //注意这里有个+1
}

struct Node{
    int l, r;  //需要查询的范围
    int start, ends; //最优的序列的起点和终点
    Node(int _start=0, int _l=0, int _r=0):start(_start), l(_l), r(_r), ends(query(_l, _r)){}
    bool operator <(const Node &a) const{
        return sum[ends]-sum[start-1]<sum[a.ends]-sum[a.start-1];
    }
};

priority_queue<Node> que;

int main()
{
    //fre;
    int n, k, L, R;
    read(n), read(k), read(L), read(R);
    _rep(i, 1, n)
        read(sum[i]), sum[i]+=sum[i-1];
    make_st(n);
    _rep(i, 1, n) //注意要防止越界
    {
        if(i+L-1>n) break;
        que.push(Node(i, i+L-1, min(n, i+R-1)));
        //printf("! i=%d, ends=%d
", i, query(i+L-1, min(i+R-1, n)));
    }
    ll ans=0;   
    while(k--)
    {
        if(que.empty()) break;
        int start=que.top().start, ends=que.top().ends;
        int l=que.top().l, r=que.top().r;
        que.pop();
        //printf("! start=%d, sum=%d, ends=%d, sum=%d
", start, sum[start-1], ends, sum[ends]);
        ans+=sum[ends]-sum[start-1];
        if(ends!=l) que.push(Node(start, l, ends-1)); //把可能形成最优解的次优解也加进去
        if(ends!=r) que.push(Node(start, ends+1, r));
    }
    write(ans), putchar('
');
    return 0;
}
原文地址:https://www.cnblogs.com/Yokel062/p/11731655.html