【主席树】[FJOI2016]神秘数

it2025-04-18  3

题目

展开 题目描述 一个可重复数字集合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]所构成的可重复数字集合的神秘数。

输入格式 第一行一个整数n,表示数字个数。

第二行n个整数,从1编号。

第三行一个整数m,表示询问个数。

以下m行,每行一对整数l,r,表示一个询问。

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

输入输出样例 输入 #1复制 5 1 2 4 9 10 5 1 1 1 2 1 3 1 4 1 5 输出 #1复制 2 4 8 8 8 说明/提示 对于100%的数据点,n,m <= 100000n,m<=100000,∑a[i] <= 10^9∑a[i]<=10 9

思路

首先我们挖一下这道题的性质 假设我们S集合中的树能表示的值域是[1,x],考虑新加进来一个数t 如果t<=x+1那么值域变为表示[1,x+t],否则值域不变,这是很容易证明的 所以暴力就是从小到大加数求值域 考虑怎么优化这个过程 设当前值域[1,x] 则ans=x+1 若小于等于ans的数的和res≥ans,则一定有未选的且小于等于ans的数,令ans=res+1即可。 反之说明答案就是ansans,直接breakbreak 因为ai比较大,用主席树维护

代码

#include<bits/stdc++.h> #define mid ((lb+rb)>>1) #define ll long long using namespace std; int cnt,root[1000001],n,m; const int inf=1e9; struct node { int l,r,sum; }seg[5000001]; void update(int &rt,int lb,int rb,int x) { seg[++cnt]=seg[rt];rt=cnt;seg[rt].sum+=x; if(lb==rb) return; (mid>=x?update(seg[rt].l,lb,mid,x):update(seg[rt].r,mid+1,rb,x)); } int query(int i,int j,int lb,int rb,int l,int r) { if(!(seg[j].sum-seg[i].sum)||r<lb|rb<l) return 0; if(lb>=l&&rb<=r) return (seg[j].sum-seg[i].sum); return query(seg[i].l,seg[j].l,lb,mid,l,r)+query(seg[i].r,seg[j].r,mid+1,rb,l,r); } int main() { scanf("%d",&n); for(int i=1,x; i<=n; i++) { root[i]=root[i-1]; scanf("%d",&x); update(root[i],1,inf,x); } scanf("%d",&m); while(m--) { int l,r; scanf("%d%d",&l,&r); int mx=0,pos=0; for(int Sum; ; ) { Sum=query(root[l-1],root[r],1,inf,mx+1,pos+1); if(!Sum) break; mx=pos+1;pos+=Sum; } printf("%d\n",pos+1); } }
最新回复(0)