【bzoj4408】[Fjoi 2016]神秘数 主席树

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,求由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,r],我们新增加一个数x,如果x∈[1,ans]则可取区间变为[1,r+x]
对于一个区间,我们假设神秘数为ans(初始ans=1)
每次把区间内小于等于ans的数累加=sum 如果sum> ans 则说明[1,sum]是可取到达,ans变为sum+1
否则说明ans已经是最小的不能取到的神秘数了。

代码

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<set>
#include<complex>
#define ll long long
#define mod 10000007
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int n,tot,a[100005],t[10000005],rt[100005],ls[10000005],rs[10000005],sz;
void insert(int &k,int p,int l,int r,int x)
{
    t[k=++sz]=t[p]+x;
    ls[k]=ls[p];rs[k]=rs[p];
    if (l==r) return;
    int mid=(l+r)>>1;
    if (x<=mid) insert(ls[k],ls[p],l,mid,x);
    else insert(rs[k],rs[p],mid+1,r,x);
}
int query(int k1,int k2,int l,int r,int x)
{
    if (r<=x) return t[k1]-t[k2];
    int mid=(l+r)>>1;
    if (x<=mid) return query(ls[k1],ls[k2],l,mid,x);
    else return t[ls[k1]]-t[ls[k2]]+query(rs[k1],rs[k2],mid+1,r,x);
}
int main()
{
    n=read();
    for (int i=1;i<=n;i++) a[i]=read(),tot+=a[i];
    for (int i=1;i<=n;i++) insert(rt[i],rt[i-1],1,tot,a[i]);
    int Case=read();
    while (Case--)
    {
        int l=read(),r=read(),ans=1;
        while (1)
        {
            int sum=query(rt[r],rt[l-1],1,tot,ans);
            if (sum<ans) break;
            ans=sum+1;
        }
        printf("%d\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值