BZOJ4408

Portal

Description

一个可重复数字集合(S)的神秘数定义为最小的不能被(S)的子集的和表示的正整数。现给出一个(n(nleq10^5))个数的数列({a_n}(Sigma a_ileq10^9))(m(mleq10^5))次询问,每次给出两个数(L,R),求集合({a_{L..R}})的神秘数。

Solution

可持久化线段树。
首先考虑如何求出集合的神秘数。定义(t)(ans),表示用不超过(t)的元素能够凑成([1,ans-1])中的所有数。初始时有(t=0,ans=1)。接下来考虑加上一个数(x)后对(ans)有何影响。若(x>ans)则依然无法凑出(ans),不变;否则能够凑出([1,ans+x-1])中的所有数,(ans=ans+x)。那么求出([t+1,ans])中所有元素的和,加到(ans)上就得到了新的(ans),新的(t)等于原(ans)。简化一下过程:初始(ans=1),每次令(ans)等于([1,ans])中所有元素的和(+1),直到(ans)不再增大为止。由于(ans)每次如果增大则至少增大(t+1),所以最多进行(log)次。
那么建立(n)棵权值线段树分别记录前若干个数中的权值分布情况,询问区间([L,R])时用线段树(R)减线段树(L-1)即可。

时间复杂度(O(mlog^2n))

Code

//[FJOI2016]神秘数
#include <cstdio>
typedef long long lint;
inline char gc()
{
    static char now[1<<16],*s,*t;
    if(s==t) {t=(s=now)+fread(now,1,1<<16,stdin); if(s==t) return EOF;}
    return *s++;
}
inline int read()
{
    int x=0; char ch=gc();
    while(ch<'0'||'9'<ch) ch=gc();
    while('0'<=ch&&ch<='9') x=x*10+ch-'0',ch=gc();
    return x;
}
const int N=1e5+10;
int n,m;
int cnt,rt[N];
struct node{int chL,chR; lint sum;} nd[N*32];
void update(int p) {nd[p].sum=nd[nd[p].chL].sum+nd[nd[p].chR].sum;}
void ins(int &p,int L0,int R0,int x)
{
    nd[++cnt]=nd[p]; p=cnt;
    if(L0==R0) {nd[p].sum+=x; return;}
    int mid=L0+R0>>1;
    if(x<=mid) ins(nd[p].chL,L0,mid,x);
    else ins(nd[p].chR,mid+1,R0,x);
    update(p);
}
lint optL,optR; lint qres;
void query(int p1,int p2,int L0,int R0)
{
    if(p1==p2) return;
    if(optL<=L0&&R0<=optR) {qres+=nd[p2].sum-nd[p1].sum; return;}
    int mid=L0+R0>>1;
    if(optL<=mid) query(nd[p1].chL,nd[p2].chL,L0,mid);
    if(mid<optR) query(nd[p1].chR,nd[p2].chR,mid+1,R0);
}
int main()
{
    n=read(); int maxA=1e9;
    for(int i=1;i<=n;i++)
        ins(rt[i]=rt[i-1],1,maxA,read());
    m=read();
    for(int i=1;i<=m;i++)
    {
        int L=read(),R=read();
        lint ans=1;
        while(true)
        {
            optL=1,optR=ans,qres=0,query(rt[L-1],rt[R],1,maxA);
            if(qres<ans) break; else ans=qres+1;
        }
        printf("%lld
",ans);
    }
    return 0;
}

P.S.

我又开始懒得写题解了...
本来我想权值(10^9<2^{30})所以线段树只开了(30N)个节点,但事实上应该开(31N) 0(xp )~

原文地址:https://www.cnblogs.com/VisJiao/p/BZOJ4408.html