Bzoj 4408: [Fjoi 2016]神秘数 可持久化线段树,神题

Posted 微弱的世界

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Bzoj 4408: [Fjoi 2016]神秘数 可持久化线段树,神题相关的知识,希望对你有一定的参考价值。

4408: [Fjoi 2016]神秘数

Time Limit: 10 Sec  Memory Limit: 128 MB
Submit: 177  Solved: 128
[Submit][Status][Discuss]

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

 

Source

鸣谢yyh上传

题解:

FJ神题!!!

这道题实在是迷幻。。。

根本想不到啊!!!QAQ!!!我一直在想数学方法。。。

看了题解,竟然是可持久化线段树!!!!(Orz 出题人)

好了不废话了。。。

先把序列从小到大排序。

假设当前神秘数为ans,则[1,ans-1]一定能用S集合中的数表示。然后如果当前加入一个数字a,则可以分为两类讨论。

1,若a<=ans,则当前可以将区间变长为:[1,ans+a-1]  ,然后神秘数变为ans+a。

2,若a>ans,则区间中有空,ans不变。

然后ans从1开始,每次求小于ans的数的和get,ans变为get+1。

这里用可持久化线段树维护即可。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define MAXN 100010
 4 int sum[40*MAXN],a[MAXN],root[MAXN],SIZE;
 5 struct node
 6 {
 7     int left,right;
 8 }tree[40*MAXN];
 9 int read()
10 {
11     int s=0,fh=1;char ch=getchar();
12     while(ch<0||ch>9){if(ch==-)fh=-1;ch=getchar();}
13     while(ch>=0&&ch<=9){s=s*10+(ch-0);ch=getchar();}
14     return s*fh;
15 }
16 void Add(int x,int &y,int l,int r,int add)
17 {
18     y=++SIZE;
19     sum[y]=sum[x]+add;
20     if(l==r)return;
21     tree[y].left=tree[x].left;tree[y].right=tree[x].right;
22     int mid=(l+r)/2;
23     if(add<=mid)Add(tree[x].left,tree[y].left,l,mid,add);
24     else Add(tree[x].right,tree[y].right,mid+1,r,add);
25 }
26 int query(int x,int y,int l,int r,int k)
27 {
28     if(l==r)return sum[y]-sum[x];
29     int mid=(l+r)/2;
30     if(k<=mid)return query(tree[x].left,tree[y].left,l,mid,k);
31     else return query(tree[x].right,tree[y].right,mid+1,r,k)+sum[tree[y].left]-sum[tree[x].left];
32 }
33 int main()
34 {
35     int n,tot,i,m,l,r,ans,get;
36     n=read();
37     tot=0;
38     for(i=1;i<=n;i++)a[i]=read(),tot+=a[i];
39     SIZE=0;
40     for(i=1;i<=n;i++)Add(root[i-1],root[i],1,tot,a[i]);
41     m=read();
42     for(i=1;i<=m;i++)
43     {
44         l=read();r=read();
45         ans=1;
46         while(1)
47         {
48             get=query(root[l-1],root[r],1,tot,ans);
49             if(get<ans)break;
50             ans=get+1;
51         }
52         printf("%d\n",ans);
53     }
54     return 0;
55 }

 

以上是关于Bzoj 4408: [Fjoi 2016]神秘数 可持久化线段树,神题的主要内容,如果未能解决你的问题,请参考以下文章

BZOJ4408[Fjoi 2016]神秘数 主席树神题

●BZOJ BZOJ 4408 [Fjoi 2016]神秘数

Bzoj 4408: [Fjoi 2016]神秘数 可持久化线段树,神题

bzoj4408: [Fjoi 2016]神秘数

bzoj4408[Fjoi 2016]神秘数 主席树

bzoj 4408: [Fjoi 2016]神秘数