LG 题解 P4587 [FJOI2016]神秘数


目录
  • 前置芝士
  • Description
  • Solution
  • Code

题目传送

思路挺仙的一题,不过有了思路就是个主席树的板子了

前置芝士

  • 主席树

Description

一个可重复数字集合 \(S\) 的神秘数定义为最小的不能被 \(S\) 的子集的和表示的正整数。
现在给你一个长度为 \(n\) 的序列 \(a\) ,每次询问一个 \([l,r]\),问 \([l,r]\) 区间的数组成的集合的神秘数。
\(n,m \le 10^5, \sum a_i \le 10^9\)

Solution

考虑没有区间查询时怎么做。

首先肯定要考虑有没有 \(1\) ,没有直接输出 \(1\)
先对 \(a\) 序列进行排序。
设当前我们已经表示出了 \([1,x]\),下一个添加进去的数为 \(a_i\)

  • 如果 \(a_i \le x+1\),填加后能表示出来的区间为 \([1,x] \cup [1 + a_i, x + a_i] = [1, x + a_i]\)
  • 如果 \(a_i > x + 1\),那么 \(x+1\) 就表示不出来,所以答案为 \(x+1\)

这样一次询问的复杂度是 \(O(n)\) 的。

考虑优化这个添加过程。

假设用值域在 \([1,lst]\) 区间内的数可以表示出来的区间为 \([1,now]\)
那么我们再把值域在 \([1,now+1]\) 区间内的任何数放进去都是合法的。
因为我们已经把值域在 \([1,lst]\) 内的所有数放进去了,那么我们设 \(sum = \sum a_i [lst + 1 \le a_i \le now + 1]\),即对值域在 \([lst+1, now+1]\) 内的数求和,把他们一起放进去。
那么我们已经用过的数的区间为 \([1,now + 1]\),可以表示出来的数的区间为 \([1,now+sum]\)
注:上面这块的区间均指值域区间而不是下标区间,注意区分。

上面这个值域的扩展过程类似于斐波那契数列,所以说复杂度是 \(\log (\sum a_i)\) 的。

查询一段区间内一个值域区间的和就是主席树板子做的事了。单次求和为 $\log $ 复杂度。

所以总复杂度为 \(O(n \log n \log (\sum a_i))\)

Code

/*
Work by: Suzt_ilymics
Knowledge: ??
Time: O(??)
*/
#include
#include
#include
#include
#include
#include
#include
#define int long long
#define orz cout<<"lkp AK IOI!"<> 1;
        if(pos <= mid) Insert(ls, lson[pre_], l, mid, pos, val_);
        else Insert(rs, rson[pre_], mid + 1, r, pos, val_);
    }
    int Query(int now_, int pre_, int l, int r, int L, int R) {
        if(!(val[now_] - val[pre_])) return 0;
        if(L <= l && r <= R) return val[now_] - val[pre_];
        int mid = (l + r) >> 1, res = 0;
        if(mid >= L) res += Query(ls, lson[pre_], l, mid, L, R);
        if(mid < R) res += Query(rs, rson[pre_], mid + 1, r, L, R);
        return res;
    }
}

signed main()
{
    n = read();
    for(int i = 1, x; i <= n; ++i) {
        x = read();
        Hjt::Insert(root[i], root[i - 1], 1, Max, x, x);
    }
    m = read();
    for(int i = 1, l, r; i <= m; ++i) {
       l = read(), r = read();
       int lst = 0, now = 0;
       while(1 + 2 == 3) {
           int tmp = Hjt::Query(root[r], root[l - 1], 1, Max, lst + 1, now + 1);
//           cout<<"tmp: "<