BZOJ4408: [Fjoi 2016]神秘数【主席树好题】

Description

一个可重复数字集合S的神秘数定义为最小的不能被S的子集的和表示的正整数。例如S={1,1,1,4,13},

1 = 1

2 = 1+1

3 = 1+1+1

4 = 4

5 = 4+1

6 = 4+1+1

7 = 4+1+1+1

8无法表示为集合S的子集的和,故集合S的神秘数为8。

现给定n个正整数a[1]..a[n],m个询问,每次询问给定一个区间l,r,求由a[l],a[l+1],…,a[r]所构成的可重复数字集合的神秘数。

Input

第一行一个整数n,表示数字个数。
第二行n个整数,从1编号。
第三行一个整数m,表示询问个数。
以下m行,每行一对整数l,r,表示一个询问。

Output

对于每个询问,输出一行对应的答案。

Sample Input

5
1 2 4 9 10
5
1 1
1 2
1 3
1 4
1 5

Sample Output

2
4
8
8
8

HINT

对于100%的数据点,n,m <= 100000,∑a[i] <= 10^9


思路

这是一个不套路的主席树题...真的是好题

首先发现一个性质,如果当前可以凑出来的区间是\([1,x]\)

那么如果有一个y需要被加进集合中

如果\(y\le x + 1\),那么新的可以表示出来的区间一定是\([1,x+y]\)

否则的话\(x+1\)一定不能被表示出来

但是这样需要按大小顺序考虑每一个数

所以就可以用主席数可持久化一下

然后每次因为在\([1,x+1]\)之间的数都可以被计入贡献

所以可以直接求一个前缀sum就可以了

每次就比较一下,如果不能更新答案或者没有合法答案就可以退出了

复杂度很迷我不会证明


//Author: dream_maker
#include<bits/stdc++.h>
using namespace std;
//----------------------------------------------
//typename
typedef long long ll;
//convenient for
#define fu(a, b, c) for (int a = b; a <= c; ++a)
#define fd(a, b, c) for (int a = b; a >= c; --a)
#define fv(a, b) for (int a = 0; a < (signed)b.size(); ++a)
//inf of different typename
const int INF_of_int = 1e9;
const ll INF_of_ll = 1e18;
//fast read and write
template <typename T>
void Read(T &x) {
  bool w = 1;x = 0;
  char c = getchar();
  while (!isdigit(c) && c != '-') c = getchar();
  if (c == '-') w = 0, c = getchar();
  while (isdigit(c)) {
    x = (x<<1) + (x<<3) + c -'0';
    c = getchar();
  }
  if (!w) x = -x;
}
template <typename T>
void Write(T x) {
  if (x < 0) {
    putchar('-');
    x = -x; 
  }
  if (x > 9) Write(x / 10);
  putchar(x % 10 + '0');
}
//----------------------------------------------
const int N = 1e5 + 10;
const int M = 2e6 + 10;
int rt[N], ls[M], rs[M], tot = 0;
ll sum[M];
void build(int &t, int l, int r) {
  t = ++tot;
  ls[t] = rs[t] = sum[t] = 0;
  if (l == r) return;
  int mid = (l + r) >> 1;
  build(ls[t], l, mid);
  build(rs[t], mid + 1, r);
}
void insert(int &t, int last, int l, int r, int pos, int vl) {
  t = ++tot;
  ls[t] = ls[last];
  rs[t] = rs[last];
  sum[t] = sum[last] + vl;
  if (l == r) return;
  int mid = (l + r) >> 1;
  if (pos <= mid) insert(ls[t], ls[last], l, mid, pos, vl);
  else insert(rs[t], rs[last], mid + 1, r, pos, vl);
}
ll query(int t, int last, int l, int r, int pos) {
  if (l == r) return sum[t] - sum[last];
  int mid = (l + r) >> 1;
  if (pos <= mid) return query(ls[t], ls[last], l, mid, pos);
  else return sum[ls[t]] - sum[ls[last]] + query(rs[t], rs[last], mid + 1, r, pos);
}
int n, m, q, a[N], b[N], pre[N];
int find_pow(int vl) {
  int l = 1, r = m, res = 0;
  while (l <= r) {
    int mid = (l + r) >> 1;
    if (pre[mid] <= vl) res = mid, l = mid + 1;
    else r = mid - 1;
  }
  return res;
}
int main() {
  //freopen("input.txt", "r", stdin);
  Read(n);
  fu(i, 1, n) {
    Read(a[i]);
    pre[i] = a[i];
  }
  sort(pre + 1, pre + n + 1);
  m = unique(pre + 1, pre + n + 1) - pre - 1;
  pre[m + 1] = INF_of_int;
  build(rt[0], 1, m);
  fu(i, 1, n) {
    b[i] = lower_bound(pre + 1, pre + m + 1, a[i]) - pre;
    insert(rt[i], rt[i - 1], 1, m, b[i], a[i]);
  }
  Read(q);
  while (q--) {
    int l, r; Read(l), Read(r);
    ll now = 0;
    while (1) {
      int pos = find_pow(now + 1);
      if (!pos) {++now; break;}
      int s = query(rt[r], rt[l - 1], 1, m, pos);
      if (s <= now) {
        ++now;
        break;
      }
      now = s;
    }
    Write(now), putchar('\n');
  }
  return 0;
}
posted @ 2018-10-27 22:17  Dream_maker_yk  阅读(223)  评论(0编辑  收藏  举报