E - Least Elements (树状数组+二分)

题目链接:atcoder

题目大意:

给你一个长度为 n n n 的序列,对于每个长度为 m m m 的连续子序列,求出前 k k k 小的数的和。

思路:

考虑将原数组离散化,对于每个长度为 m m m 的区间,需要用 tr1 维护出离散化后的每个下标次数,tr2 表示在离散化下表下原数组的和,每次查询答案,先二分出最后一个小于等于 k k k 的下标 l l l,如果 l = = k l == k l==k ,答案就是 t r 2. s u m ( 0 , k ) tr2.sum(0,k) tr2.sum(0,k), 否则进一步处理得出 l + 1 l+1 l+1 位置的原数组的值,答案加上相应的值即可。

代码:

#include <bits/stdc++.h>
#define int long long
constexpr int N = 2E5 + 10;
using namespace std;
using LL = long long;

int lowbit(int x) { return x & -x; }
int tr[2][N];
int n, m, k;
int sum(int u, int x) {
    int res = 0;
    while (x) {
        res += tr[u][x];
        x -= lowbit(x);
    }
    return res;
}
void add(int u, int x, int c) {
    while (x <= n) {
        tr[u][x] += c;
        x += lowbit(x);
    }
}
int query() {
    int l = 1, r = n;
    while (l < r) {
        int mid = l + r + 1 >> 1;
        if (sum(0, mid) <= k)
            l = mid;
        else
            r = mid - 1;
    }
    if (sum(0, l) == k) return sum(1, l);
    int dx = k - sum(0, l);
    int val = (sum(1, l + 1) - sum(1, l)) / (sum(0, l + 1) - sum(0, l));
    return sum(1, l) + dx * val;
}
signed main() {
    cin >> n >> m >> k;
    vector<int> a(n + 1);
    for (int i = 1; i <= n; i++) cin >> a[i];
    auto b = a;
    sort(b.begin(), b.end());
    unordered_map<int, int> mp;
    int cnt = 0;
    mp[b[1]] = ++cnt;
    for (int i = 2; i <= n; i++) {
        if (b[i] == b[i - 1])
            mp[b[i]] = cnt;
        else
            mp[b[i]] = ++cnt;
    }
    for (int i = 1; i <= m; i++) {
        int p = mp[a[i]];
        add(0, p, 1);
        add(1, p, a[i]);
    }
    cout << query() << ' ';
    for (int i = m + 1; i <= n; i++) {
        int p = mp[a[i - m]];
        add(0, p, -1);
        add(1, p, -a[i - m]);
        p = mp[a[i]];
        add(0, p, 1);
        add(1, p, a[i]);
        cout << query() << ' ';
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值