zoukankan      html  css  js  c++  java
  • 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 )~

  • 相关阅读:
    Centos7安装Python3
    mysql+centos7+主从复制
    Python开发之virtualenv和virtualenvwrapper详解
    Linux基础系统优化(二)
    Linux基础系统优化(一)
    Windows 10版本区别
    HTML5常用的语义化标签
    关于efk多服务器多日志合并收集
    离线状态安装docker容器
    Docker部署jenkins+sonar+gitlab代码检测系统
  • 原文地址:https://www.cnblogs.com/VisJiao/p/BZOJ4408.html
Copyright © 2011-2022 走看看