zoukankan      html  css  js  c++  java
  • 快速理解整体二分-lougu3834静态区间第k大

    首先,是一个整体二分的入门题

    https://www.luogu.org/problem/P3834

    题目很简单,要求快速求解静态区间$[l,r]$的第$K$大元素值

    好,先思考一个简单问题

    给一个序列,求一次这个序列的第$k$大

    有一种二分思路是,我们用计数排序,然后维护它的前缀和

    每次直接二分第$k$大的答案$mid$,如果$[1-mid]$的计数前缀和$<=k$,则说明$mid>=$真实答案,否则小于

    这个等价于,每次二分,我们都跑一遍全部序列,只有小于$mid$的元素是有效的,,然后拿有效的元素个数和$k$做比较

    那么继续看:

    对于本题,首先把操作全部放在一起,按照时间顺序就行

    注意,对于初始数列的赋值,我们也可以视为一种操作,按顺序放在所有询问的前面就行了

    简单来说,保存下这些信息:

    {操作类型$tp$,操作区间下限$l$,操作区间上限$r$,操作值$x$/查询值$k$,操作的编号$id$}

    而对于赋值操作来说,显然其区间下限和上限都为该点的下标

    然后整体二分开始:

    我们一口气二分所有的询问,

    现在我们记当前询问所回答的操作段为$[s,t]$,二分答案$mid$

    即,我们处理$(s,t,l,r)$这个子问题

    原来的操作序列记为$q$,再开两个辅助的操作序列$ql,qr$;

    如果是赋值操作:

    1.$x$小于我们二分的答案$mid$,那么,我们肯定会受到影响,所以在$bit$上在它所赋值的位置上$+1$,

      再把这个操作放入$ql$中,因为它可能对更小的$mid$有影响

      这里我们类比到一开始的简单问题,就可以理解了,

      $bit$实际上维护了整个序列上的有效点,让我们可以快速查找任意区间内的有效点个数

    2.$x$大于$mid$,那么我们就不需要管,因为我们暂时不会受到影响,整个点是无效的,我们直接放入qr中,因为它需要对更大mid值的有影响

    如果是查询操作:

    1.我们类比一开始的简单问题,显然,如果查询的区间$[q[i].l,q[i].r]$内的有效点个数$cnt$小于$k$个,那么我们应该增加$mid$的值,所以放进$qr$中,

      但是由于已经拥有$cnt$个满足条件的了,而下一步二分的时候,值域为$mid$以下的操作我们会放入另外一个子问题,

           因此,需要把$k$减少$cnt$再放入$qr$

    2.如果小于等于$k$个,我们可以尝试减少$mid$的值,所以直接放入$ql$中

    处理完$[s,t]$的操作之后,我们将之前做过的赋值全部撤销

    最后,显然地,对于$[s,t]$的所有操作,我们分为了2组,$q$l和$qr$,现在我们把$ql$放在前面,$qr$放在后面,

    这样我们就对$[s,t]$的操作进行了一次分组,左边$ql$个操作是对答案$[l,mid]$的进行回答/影响的,右边$qr$个操作是对答案$[mid+1,r]$的进行操作/影响的

    接着继续二分$(s,s+ql-1,l,mid)$和$(s+ql,mid+1,r)$两个子问题就行了

    最后递归到$l==r$的时候,显然此时$[s,t]$的答案就都找到了

    复杂度可以认为是$(n+m)log(n+m)log(ValueDomain)$的,

    在该问题下,尽管常数很小,但是由于比主席树多了一个log,所以还是慢了2-3倍

    但它的优越之处在于,对于静态和动态区间第$k$大,其复杂度都是一样的,

    在我们用它解决动态区间第$K$大,三维偏序之类的问题时,就能体现出优越性了

    本题代码如下:

    #include<bits/stdc++.h>
    #define rep(ii,a,b) for(int ii=a;ii<=b;++ii)
    #define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
    using namespace std;//head
    const int maxn=4e5+10,maxm=2e6+10;
    int casn,n,m,k,cnt;
    class bit{public:
      int node[maxn];
      inline int lb(int x) {return x&(-x);}
      inline void update(int pos,int val){
        for(;pos<=n;pos+=lb(pos)) node[pos]+=val;
      }
      inline int ask(int pos){
        int sum=0;
        for(;pos>0;pos-=lb(pos)) sum+=node[pos];
        return sum;
      }
      inline int query(int l,int r){
        return ask(r)-ask(l-1);
      }
    }tree;
    struct node{int x,l,r,k;}q[maxn],ql[maxn],qr[maxn];
    int ans[maxn],b[maxn];
    namespace divide{
      void dc(int s,int t,int l,int r){ 
        if(l>r||s>t) return ;
        if(l==r){
          rep(i,s,t) if(q[i].x)ans[q[i].x]=l;
          return ;
        }
        int mid=(l+r)>>1,cntl=0,cntr=0,tmp;
        rep(i,s,t){
          if(!q[i].x){
            if(q[i].k<=mid) ql[++cntl]=q[i],tree.update(q[i].l,1);
            else qr[++cntr]=q[i];        
          }else{
            tmp=tree.query(q[i].l,q[i].r);
            if(tmp<q[i].k)q[i].k-=tmp,qr[++cntr]=q[i];
            else ql[++cntl]=q[i];
          }
        }
        rep(i,1,cntl){
          q[s-1+i]=ql[i];
          if(!ql[i].x) tree.update(ql[i].l,-1);
        }
        rep(i,1,cntr)q[s-1+cntl+i]=qr[i];
        dc(s,s+cntl-1,l,mid);dc(s+cntl,t,mid+1,r);
      }
    }
    int main() {IO;
      cin>>n>>m;
      rep(i,1,n) cin>>b[i];
      rep(i,1,n) q[i]=(node){0,i,i,b[i]};
      int l,r,k;
      rep(i,1,m){
        cin>>l>>r>>k;
        q[n+i]=(node){i,l,r,k};
      }
      divide::dc(1,n+m,-1e9-10,1e9+10);
      rep(i,1,m) cout<<ans[i]<<endl;
    }
    
  • 相关阅读:
    #虚树,树形dp#CF613D Kingdom and its Cities
    #搜索,容斥#洛谷 2567 [SCOI2010]幸运数字
    #三分#洛谷 5931 [清华集训2015]灯泡
    windows中日期自动替换
    oracle的tablespaces使用情况监控
    k8s配置master运行pod
    redis配置数据持久化
    Centos7清理僵尸进程
    ZOJ 1078 Palindrom Numbers
    OCP 071【中文】考试题库(cuug整理)第33题
  • 原文地址:https://www.cnblogs.com/nervendnig/p/11495261.html
Copyright © 2011-2022 走看看