神秘数
时间限制: 1 Sec 内存限制: 256 MB
题目描述
一个可重复数字集合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,表示一个询问。
输出
对于每个询问,输出一行对应的答案。
样例输入
5
1 2 4 9 10
5
1 1
1 2
1 3
1 4
1 5
样例输出
2
4
8
8
8
【数据范围】
对于10%的数据点,n,m <= 10
对于30%的数据点,n,m <= 1000
对于60%的数据点,n,m <= 50000
对于100%的数据点,n,m <= 100000,∑a[i] <= 109
来源
FJOI2016
题解
写在前面:就是这道题害我省选爆零,直接退役。
进入正题:
考虑一段数列,如何计算它的神秘数?
将其排序,若sum[i-1]>s[i]+1,那么神秘数即为sum[i-1]。
对于一组询问,每次暴力更新区间内>=sum+1的数,当出现斐波那契数列时达到上界。
用一棵主席树维护区间和。
注意细节,记得对拍,不然就要像我一样爆零退役了。
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#define N 100010
#define inf 2100000000
using namespace std;
int n,m,tot,s[N],id[N],num[N],rt[N],cnt;
struct node{int lc,rc,sum;}t[N*18];
bool cmp(const int &a,const int &b){return s[a]<s[b];}
class functional_seg_tree
{
public:
void modify(int &x,int pre,int l,int r,int des,int val)
{
x=++cnt;t[x]=t[pre];t[x].sum+=val;
if(l==r)return;
int mid=l+r>>1;
if(des<=mid)modify(t[x].lc,t[pre].lc,l,mid,des,val);
else modify(t[x].rc,t[pre].rc,mid+1,r,des,val);
}
int qry(int x,int l,int r,int ql,int qr)
{
if(ql<=l&&r<=qr)return t[x].sum;
int mid=l+r>>1,res=0;
if(ql<=mid)res+=qry(t[x].lc,l,mid,ql,qr);
if(qr>mid)res+=qry(t[x].rc,mid+1,r,ql,qr);
return res;
}
}T;
int main()
{
int a,b;
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&s[i]),id[i]=i;
sort(id+1,id+n+1,cmp);
for(int i=1;i<=n;i++)
{
num[++tot]=s[id[i]];
T.modify(rt[tot],rt[tot-1],1,n,id[i],s[id[i]]);
while(s[id[i]]==s[id[i+1]])
i++,T.modify(rt[tot],rt[tot],1,n,id[i],s[id[i]]);
}
scanf("%d",&m);
while(m--)
{
scanf("%d%d",&a,&b);
int ans=0,pre=0;
while(1)
{
int l=1,r=tot,pos=0;
while(r-l>1)
{
int mid=l+r>>1;
if(num[mid]<=ans+1)l=mid;
else r=mid-1;
}
pos=num[r]<=ans+1?pos=r:l;
if(pos==pre)break;
ans=T.qry(rt[pre=pos],1,n,a,b);
}
printf("%d\n",ans+1);
}
return 0;
}