P2048 [NOI2010] ST + 二叉堆

题意

传送门 P2048 [NOI2010] 超级钢琴

题解

将区间和转化为前缀和 s u m sum sum 之差,那么问题转化为求满足条件的点对 ( i , j ) , i < j (i,j),i<j (i,j),i<j s u m [ j ] − s u m [ i ] sum[j]-sum[i] sum[j]sum[i] 的最大值。对于每个点 i i i,以其为左界可取得的最大区间和为 max i + L − 1 i + R − 1 s u m [ j ] − s u m [ i − 1 ] \text{max}_{i+L-1}^{i+R-1}sum[j]-sum[i-1] maxi+L1i+R1sum[j]sum[i1] 由于 i i i 固定,只关注第一项可取得的最大值。设第一项取得最大值的位置为 p p p,那么以 i i i 为左界可取得的区间次大值对应的 p ′ p' p 为取得下式的对应位置 max ( max i + L − 1 p − 1 s u m [ j ] , max p + 1 i + R − 1 s u m [ j ] ) \text{max}\big(\text{max}_{i+L-1}^{p-1}sum[j],\text{max}_{p+1}^{i+R-1}sum[j]\big) max(maxi+L1p1sum[j],maxp+1i+R1sum[j]) 区间个数为 N 2 N^2 N2,为了减少搜索的区间数,枚举左界,仅拓展其最大区间和,并用大根堆维护;不断取出堆顶,将对应的区间次大和的可能位置插入二叉堆。

使用 S T ST ST 算法实现 R M Q RMQ RMQ,总时间复杂度为 O ( ( N + K ) log ⁡ N ) O((N+K)\log N) O((N+K)logN)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 500005, maxlg = 20;
int N, K, L, R, A[maxn], sum[maxn];
int lg[maxn], mx[maxn][maxlg], pos[maxn][maxlg];
struct node
{
    int d, i, p, l, r;
    bool operator<(const node &b) const { return d < b.d; }
};
priority_queue<node> Q;

inline int read()
{
    int x = 0, f = 0;
    char c = 0;
    for (; c < '0' || c > '9'; c = getchar())
        if (c == '-')
            f = 1;
    for (; c >= '0' && c <= '9'; c = getchar())
        x = (x << 1) + (x << 3) + c - '0';
    return f ? -x : x;
}

void st_init()
{
    lg[0] = -1;
    for (int i = 1; i <= N; ++i)
        lg[i] = lg[i - 1] + (1 << (lg[i - 1] + 1) == i);
    for (int i = 1; i <= N; ++i)
        mx[i][0] = sum[i], pos[i][0] = i;
    for (int k = 1; k <= lg[N]; ++k)
        for (int i = 1, j; i + (1 << k) - 1 <= N; ++i)
        {
            j = i + (1 << (k - 1));
            mx[i][k] = max(mx[i][k - 1], mx[j][k - 1]);
            pos[i][k] = mx[i][k - 1] > mx[j][k - 1] ? pos[i][k - 1] : pos[j][k - 1];
        }
}

inline int ask(int l, int r)
{
    int k = lg[r - l + 1], m = r - (1 << k) + 1;
    return mx[l][k] > mx[m][k] ? pos[l][k] : pos[m][k];
}

inline int get(int l, int r) { return sum[r] - sum[l - 1]; }

int main()
{
    N = read(), K = read(), L = read(), R = read();
    for (int i = 1; i <= N; ++i)
        A[i] = read(), sum[i] = A[i] + sum[i - 1];
    st_init();
    for (int i = 1, p, l, r; i <= N; ++i)
    {
        l = i + L - 1, r = i + R - 1;
        if (l > N)
            continue;
        r = min(r, N), p = ask(l, r);
        Q.push(node{get(i, p), i, p, l, r});
    }
    ll res = 0;
    for (int i = 1, p; i <= K; ++i)
    {
        node t = Q.top();
        Q.pop();
        res += t.d;
        if (t.p > t.l)
            p = ask(t.l, t.p - 1), Q.push(node{get(t.i, p), t.i, p, t.l, t.p - 1});
        if (t.p < t.r)
            p = ask(t.p + 1, t.r), Q.push(node{get(t.i, p), t.i, p, t.p + 1, t.r});
    }
    printf("%lld\n", res);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值