1. 程式人生 > >[FJOI2016]神祕數(腦洞+可持久化)

[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; }