bzoj 4408: [Fjoi 2016]神秘数 数学 可持久化线段树 主席树

https://www.lydsy.com/JudgeOnline/problem.php?id=4299

一个可重复数字集合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](l<=r),求由a[l],a[l+1],…,a[r]所构成的可重复数字集合的神秘数。

Input

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

Output

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

题解
A : 
将a从小到大排序,设当前神秘数为ans,扫到了a[i],那么
1. a[i] < ans 时, ans = ans + a[i] ;
2. a[i] > ans时, ans就是最小的神秘数, 跳出循环. 
B : 
我们也可以发现神秘数简化推法,ans初始为1,那么下一个ans为(sigma (a[i]<=ans) a[i])+1 . (用A部分方法压缩的想法来思考 [ lastans , nowans ) 区间内数的填充), 能够看出sigma的次数是log级的. 
此时我们需要维护的是任意区间内的sigma, 主席树可以实现. 
这里的主席树并没有离散化,因为主席树只需要建 n*( log总长 ) 个点, 这样写更方便 ( 常数变大了但是并不是很影响复杂度 ), 离散化也阔以, 不过注意一下查找的时候用upper_bound. 
 
(在这里mark一下lower_bound和upper_bound的方向)
 
 
 1 #include<iostream>
 2 #include<cstdio>
 3 #include<algorithm>
 4 #include<cstring>
 5 #include<cmath>
 6 using namespace std;
 7 const int maxn=100010;
 8 int n,m,cnt=0,tot=0;
 9 int a[maxn]={},rt[maxn]={};
10 int sum[maxn*60]={},lc[maxn*60]={},rc[maxn*60]={};
11 int read(){
12     int w=0,f=1;char ch=getchar();
13     while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
14     while(ch>='0'&&ch<='9'){w=w*10+ch-'0';ch=getchar();}
15     return w*f;
16 }
17 void Build(int l,int r,int y,int &x,int v){
18     x=++tot;sum[x]=sum[y]+v;
19     if(l==r)return;
20     lc[x]=lc[y];rc[x]=rc[y];
21     int mid=(l+r)/2;
22     if(v<=mid) Build(l,mid,lc[y],lc[x],v);
23     else Build(mid+1,r,rc[y],rc[x],v);
24 }
25 int Query(int l,int r,int x,int y,int v){
26     if(l==r)return sum[y]-sum[x];
27     //cout<<sum[y]<<sum[x]<<l<<r<<endl;
28     int mid=(l+r)/2;
29     if(v<=mid) return Query(l,mid,lc[x],lc[y],v);
30     else return Query(mid+1,r,rc[x],rc[y],v)+sum[lc[y]]-sum[lc[x]];
31 }
32 int main(){
33     //freopen("a.in","r",stdin);
34     //freopen("a.out","w",stdout);
35     n=read();
36     for(int i=1;i<=n;i++){a[i]=read();cnt+=a[i];}
37     for(int i=1;i<=n;i++)Build(1,cnt,rt[i-1],rt[i],a[i]);
38     m=read();
39     for(int i=1;i<=m;i++){
40         int l=read();int r=read();
41         int ans=1;
42         for(;;){
43             int z=Query(1,cnt,rt[l-1],rt[r],ans);
44             //cout<<z<<endl;
45             if(z<ans)break;
46             ans=z+1;
47         }
48         printf("%d\n",ans);
49     }
50     return 0;
51 }
View Code

 

 

 

 

posted @ 2018-04-08 21:41  鲸头鹳  阅读(190)  评论(0编辑  收藏  举报