BZOJ 4408 FJOI2016 神秘数 可持久化线段树

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](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,如果有,那么1就可以凑出来,同时可以发现如果有x个1,那么1~x都可以凑出来。那么考虑1~x范围内的所有数字,你可发现任何一个非1的数字都可以使我们可以连续表达的数字区间增大了(脑补一个移动的窗口),1~x区间内能表示出的最大数字就是 x+(区间内小于等于x的最大值)。仔细一想这正是一个同构子问题!

每一次都这样做,我们最多会扩大几次考虑范围呢?不难发现只要还有答案成立,这一次扩大之后的考虑范围至少是上一次的两倍,所以最多也就log级别。

于是我们只需要维护区间内小于等于某个数字的和就可以了,开心上一发主席树板子。

 

 1 #include
 2 #include
 3 #include
 4 #include
 5 #include
 6 #include
 7 #include
 8 #include<set>
 9 #include
10 #include
11 #include
12 using namespace std;
13 const int MAXN=100005;
14 
15 int N,M,AA[MAXN],rank[MAXN],tot;
16 struct persistable_segment_tree{
17     static const int maxn=1900005;
18     static const int maxm=100005;
19     int root[maxm],np,lc[maxn],rc[maxn],sum[maxn];
20     persistable_segment_tree(){ np=0; }
21     void pushup(int now){ sum[now]=sum[lc[now]]+sum[rc[now]]; }
22     void build(int &now,int L,int R){
23         now=++np,lc[now]=rc[now]=sum[now]=0;
24         if(L==R) return;
25         int m=L+R>>1;
26         build(lc[now],L,m); build(rc[now],m+1,R);
27     }
28     int copynode(int now){
29         np++,lc[np]=lc[now],rc[np]=rc[now],sum[np]=sum[now];
30         return np;
31     }
32     void update(int &now,int L,int R,int pos,int v){
33         now=copynode(now);
34         if(L==R){ sum[now]+=v; return; }
35         int m=L+R>>1;
36         if(pos<=m) update(lc[now],L,m,pos,v);
37         else update(rc[now],m+1,R,pos,v);
38         pushup(now);
39     }
40     int query(int xx,int yy,int L,int R,int A,int B){
41         if(A<=L&&R<=B) return sum[yy]-sum[xx];
42         int m=L+R>>1;
43         if(B<=m) return query(lc[xx],lc[yy],L,m,A,B);
44         if(A>m) return query(rc[xx],rc[yy],m+1,R,A,B);
45         return query(lc[xx],lc[yy],L,m,A,B)+query(rc[xx],rc[yy],m+1,R,A,B);
46     }
47 }st;
48 
49 void data_in()
50 {
51     scanf("%d",&N);
52     for(int i=1;i<=N;i++) scanf("%d",&AA[i]);
53     scanf("%d",&M);
54 }
55 void work()
56 {
57     memcpy(rank,AA,sizeof(AA));
58     sort(rank+1,rank+N+1);
59     tot=unique(rank+1,rank+N+1)-rank;
60     st.build(st.root[0],1,tot-1);
61     for(int i=1;i<=N;i++){
62         st.root[i]=st.root[i-1];
63         st.update(st.root[i],1,tot-1,lower_bound(rank+1,rank+tot,AA[i])-rank,AA[i]);
64     }
65     int l,r,pos,ans,tmp;
66     for(int i=1;i<=M;i++){
67         scanf("%d%d",&l,&r);
68         pos=upper_bound(rank+1,rank+tot,1)-rank-1,ans=1;
69         while(pos){
70             tmp=st.query(st.root[l-1],st.root[r],1,tot-1,1,pos);
71             if(tmpbreak;
72             ans=tmp+1;
73             if(pos==tot-1) break;
74             pos=upper_bound(rank+1,rank+tot,ans)-rank-1;
75         }
76         printf("%d\n",ans);
77     }
78 }
79 int main()
80 {
81     data_in();
82     work();
83     return 0;
84 }

 

转载于:https://www.cnblogs.com/KKKorange/p/8572176.html

你可能感兴趣的:(BZOJ 4408 FJOI2016 神秘数 可持久化线段树)