Codeforces Round 429 (Div. 1) D. Destiny(可持久化线段树)

原题链接:D. Destiny


题目大意:


给出一个长度为 n n n 的数组 a a a,再给出 q q q 次询问。

每次询问给出三个参数 l , r , k l,r,k l,r,k ,询问区间 [ l , r ] [l,r] [l,r] 中是否存在某个数字 a i a_{i} ai 的出现次数大于等于 ⌈ r − l + 1 k ⌉ \lceil \frac{r-l+1}{k}\rceil krl+1 ,如果存在则输出最小的那个数字,否则输出 − 1 -1 1

解题思路:


看到出现次数,种类数之类的东西就可以联想到主席树。不过这题有很多写法,随机化,莫队之类的做法,这里讲一下主席树的做法。

我们先按照下标构建一颗主席树,维护每个数字出现的次数,这样我们就可以通过相减来获得区间的某个数字的出现次数。

关于询问,我们考虑做这样一个暴力:

  • 因为是求最小值,所以我们优先暴力地递归左儿子,前提是左儿子所有数字出现次数的和大于等于 ⌈ r − l + 1 k ⌉ \lceil \frac{r-l+1}{k}\rceil krl+1
  • 假设左儿子不满足条件或者没找到对应要找的数字,那么继续暴力地递归右儿子,前提是左儿子所有数字出现次数的和大于等于 ⌈ r − l + 1 k ⌉ \lceil \frac{r-l+1}{k}\rceil krl+1
  • 假设左右儿子都不满足就返回一个无穷大的值。

这样做的复杂度看似是 O ( n 2 log ⁡ n ) O(n^{2} \log n) O(n2logn) 的,实则不然。

注意到我们每次递归的前提是,要满足所有数字出现次数的和大于等于 ⌈ r − l + 1 k ⌉ \lceil \frac{r-l+1}{k}\rceil krl+1 才会进入儿子节点。

在这里我们分析一种最坏的情况,假设我们每次都必须要递归到叶子节点(或刚好递归到叶子节点的上一层)才能返回。

那么此时肯定是在区间 [ l , r ] [l,r] [l,r] 内有 k k k 个数字都满足出现次数 c n t a i ≥ ⌈ r − l + 1 k ⌉ cnt_{a_i} \geq \lceil \frac{r-l+1}{k}\rceil cntaikrl+1 的情况。

显然复杂度此时最坏相当于做了 k k k 次独立的查询,因此单次查询最坏复杂度为 O ( k log ⁡ n ) O(k\log n) O(klogn) ,而不是 O ( n log ⁡ n ) O(n \log n) O(nlogn)

显然可以通过,注意一些代码细节即可

时间复杂度: O ( q k log ⁡ n ) O(qk\log n) O(qklogn)

#include <bits/stdc++.h>
using namespace std;

using PII = pair<int, int>;
using i64 = long long;

const int N = 3e5 + 1;

struct Segtree {
    int l, r, sum;
} seg[N << 5];

#define lson seg[k].l, l, mid
#define rson seg[k].r, mid + 1, r
#define sum(x) seg[x].sum
#define ls(x) seg[x].l
#define rs(x) seg[x].r

int idx = 0;
int creat(int pre) {
    seg[++idx] = seg[pre];
    return idx;
}

int Modify(int k, int l, int r, int p) {
    int cur = creat(k);
    ++sum(cur);
    if (l == r) {
        return cur;
    }
    int mid = l + r >> 1;
    if (p <= mid) {
        ls(cur) = Modify(lson, p);
    } else {
        rs(cur) = Modify(rson, p);
    }
    return cur;
}

const int INF = 1e9;

int Query(int pre, int k, int l, int r, int kth) {
    if (l == r) {
        return l;
    }
    //注意一下这里的查询细节
    int mid = l + r >> 1, res = INF;
    if (sum(ls(k)) -  sum(ls(pre)) >= kth) {
        res = Query(ls(pre), lson, kth);
    }
    if (res == INF && sum(rs(k)) - sum(rs(pre)) >= kth) {
        res = Query(rs(pre), rson, kth);
    }
    return res;
}

void solve() {
    int n, q;
    cin >> n >> q;

    vector<int> ver(n + 1);
    for (int i = 1; i <= n; ++i) {
        int val;
        cin >> val;
        ver[i] = Modify(ver[i - 1], 1, n, val);
    }

    for (int i = 1; i <= q; ++i) {
        int l, r, k;
        cin >> l >> r >> k;

        k = (r - l + 1) / k + 1;
        int res = Query(ver[l - 1], ver[r], 1, n, k);
        if (res == INF) {
            cout << -1 << "\n";
        } else {
            cout << res << "\n";
        }
    }
}

signed main() {

    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    int t = 1; //cin >> t;
    while (t--) solve();

    return 0;
}
  • 11
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柠檬味的橙汁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值