[FJOI2016]神秘数(脑洞+可持久化)

题目描述

一个可重复数字集合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]所构成的可重复数字集合的神秘数。

题解

加入我们查询的区间为l-r。

我们先查询有几个1,然后发现有k个,那么然后我们再查询1-k+1有多少数,如果大于等于k+1的话,那么1到k+1都能表出。

重复这个过程即可,最多跳log次。

代码

#include<iostream>
#include<cstdio>
#define N 100002
using namespace std;
typedef long long ll;
const int maxn=1e9;
ll tr[N*32],a[N];
int L[N*32],R[N*32],tot,n,m,T[N];
inline int rd(){
    int x=0;char c=getchar();bool f=0;
    while(!isdigit(c)){if(c=='-')f=1;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return f?-x:x;
}
void ins(int &cnt,int pre,int l,int r,ll x){
    cnt=++tot;
    tr[cnt]=tr[pre]+x;L[cnt]=L[pre];R[cnt]=R[pre];
    if(l==r)return;
    int mid=(l+r)>>1;
    if(mid>=x)ins(L[cnt],L[pre],l,mid,x);
    else ins(R[cnt],R[pre],mid+1,r,x); 
}
ll query(int cnt,int pre,int l,int r,ll x){
//    cout<<cnt<<" "<<pre<<" "<<l<<" "<<r<<" "<<tr[cnt]<<" "<<tr[pre]<<endl; 
    if(!cnt)return 0;
    if(r<=x)return tr[cnt]-tr[pre];
    int mid=(l+r)>>1;
    if(mid<x)return tr[L[cnt]]-tr[L[pre]]+query(R[cnt],R[pre],mid+1,r,x);
    else return query(L[cnt],L[pre],l,mid,x);
}
int main(){
    n=rd();int m;
    for(int i=1;i<=n;++i)a[i]=rd(),ins(T[i],T[i-1],1,maxn,a[i]);
    m=rd();int l,r;
    while(m--){
        l=rd();r=rd();
    //    cout<<"****"<<endl;
        ll ans=1,now=1;
        while(ans<=maxn){
        //    cout<<ans<<endl;
            ans=query(T[r],T[l-1],1,maxn,ans);         
        //    cout<<ans<<" "<<now<<endl; 
            if(ans<now)break;else now=ans+1,ans=now;
        }
        printf("%lld\n",ans+1);
    }
    return 0;
}

转载于:https://www.cnblogs.com/ZH-comld/p/10188448.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值