luoguP4587[FJOI2016]神秘数字(主席树)

本文详细解析洛谷P4587神秘数问题,介绍如何使用主席树快速处理区间内的神秘数查询,通过排序和前缀和优化算法效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原题面

洛谷P4587

题目描述

定义一个可重复数字集合\(S\)的神秘数为最小的不能被\(S\)的子集的元素和表示的数
例如:\(S = \{1,1,1,4,13\}\)
则:\(1=1 \\ 2 = 1 + 1 \\ 3 = 1 + 1 + 1 \\ 4 = 4 \\ 5 = 1 + 4 \\ 6 = 1 + 1 + 4 \\ 7 = 1 + 1 + 1 + 4\)
8不能被\(S\)的子集和表示,所以\(S\)的神秘数是8
现有一个长为\(n\)的序列,\(m\)次询问,每次询问输入\(l,r\),求\(S=\{a_i | l \le i \le r \}\)的神秘数

解析

先考虑一个询问怎么处理
\(l\)\(r\)的数排序后考虑依次加入每一个数
假设当前可以凑出\([1, t)\)的数,那么计算\(sum = \sum_{a_i \le t} {a_i}\)
如果\(sum < t\),显然\(t\)就是答案
如果\(sum \ge t\),说明凑出\([1, t)\)后一定剩下一个小于等于\(t\)的数,那么\([1, sum]\)的数也可以凑出来,\(t\)移动到\(sum + 1\)
然后考虑多组询问,我们要快速查询\([l, r]\)内小于等于\(t\)\(a_i\)之和,就可以套主席树了

代码

#include <cstdio>
#include <cstring>
#include <iostream>
#define MAXN 100005
#define MAXA 1000000000

typedef long long LL;
struct ChairmanTree {
    struct Node {
        Node *ls, *rs;
        int sum;
    } * root[MAXN];
    void build();
    void build(Node *, Node *, int, int, int);
    int query(Node *, Node *, int, int, int, int);
} T;
int N, M, ans;
int a[MAXN];

char gc();
LL read();
void print(LL);
int main() {
    N = read();
    for (int i = 1; i <= N; ++i) a[i] = read();
    T.build();
    M = read();
    while (M--) {
        int l = read() - 1, r = read();
        ans = 1;
        while (1) {
            int s = T.query(T.root[l], T.root[r], 1, MAXA, 1, ans);
            if (s >= ans)
                ans = s + 1;
            else
                break;
        }
        print(ans);
        putchar('\n');
    }
    return 0;
}
void ChairmanTree::build() {
    for (int i = 1; i <= N; ++i) build(root[i - 1], root[i] = new Node(), 1, MAXA, a[i]);
}
void ChairmanTree::build(Node *pre, Node *cur, int L, int R, int v) {
    if (L == R)
        cur->sum = (pre ? pre->sum : 0) + v;
    else {
        int mid = (L + R) >> 1;
        if (v <= mid) {
            if (pre) cur->rs = pre->rs;
            build(pre ? pre->ls : NULL, cur->ls = new Node(), L, mid, v);
        } else {
            if (pre) cur->ls = pre->ls;
            build(pre ? pre->rs : NULL, cur->rs = new Node(), mid + 1, R, v);
        }
        cur->sum = 0;
        if (cur->ls) cur->sum += cur->ls->sum;
        if (cur->rs) cur->sum += cur->rs->sum;
    }
}
int ChairmanTree::query(Node *pre, Node *cur, int L, int R, int l, int r) {
    if (!cur) return 0;
    if (L >= l && R <= r) return cur->sum - (pre ? pre->sum : 0);
    int mid = (L + R) >> 1, res = 0;
    if (l <= mid) res += query(pre ? pre->ls : NULL, cur->ls, L, mid, l, r);
    if (r > mid) res += query(pre ? pre->rs : NULL, cur->rs, mid + 1, R, l, r);
    return res;
}
inline char gc() {
    static char buf[1000000], *p1, *p2;
    if (p1 == p2) p1 = (p2 = buf) + fread(buf, 1, 1000000, stdin);
    return p1 == p2 ? EOF : *p2++;
}
inline LL read() {
    LL res = 0;
    char ch = gc();
    while (ch < '0' || ch > '9') ch = gc();
    while (ch >= '0' && ch <= '9') res = (res << 1) + (res << 3) + ch - '0', ch = gc();
    return res;
}
inline void print(LL x) {
    static int buf[30];
    if (!x)
        putchar('0');
    else {
        while (x) buf[++buf[0]] = x % 10, x /= 10;
        while (buf[0]) putchar('0' + buf[buf[0]--]);
    }
}
//Rhein_E

转载于:https://www.cnblogs.com/Rhein-E/p/10394493.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值