zoukankan      html  css  js  c++  java
  • 【基础操作】整体二分概述

    整体二分是一个常数小的离线做法。

    这篇讲 $CDQ$ 的文章里提到了其一个分支——整体二分。

    整体二分的适用性

    有一些问题,在有多组操作(一开始赋初值也算操作)但只有一组询问的情况下(当然这组询问正常情况下就放在最后的,不然它后面的操作是摆着玩的),可以二分这个询问的答案。

    二分的时间复杂度是 $O(log(n))$,验证一个答案是大了还是小了的时间复杂度是 $O(n)$(或者是这个级别的,差不多不到 $O(n^2)$ 就行),总时间复杂度是 $O(n imes log(n))$(或者是这个级别的)。

    对于很多组询问的情况,设询问组数为 $Q$,如果对每组都二分,时间复杂度是 $O(Q imes n imes log(n))$ 级别的,对于祖传数据(全 $100000$)直接升天。

    然后我们发现,有一些问题的修改对询问的影响非常有特性,支持高效维护和查询。这时候就可以把所有操作(包括修改和查询)放到一块二分。

    总之,整体二分只能在满足以下条件的情况下使用:

      - 单组询问可以二分

      - 存在高效的数据结构维护修改对询问的影响(了解整体二分的应该知道,像区间修改这种的就不存在)

      - 题目可以离线做(废话)

      ……

     

    主席树(模板)

    询问静态区间第 $k$ 小。$n,Qle 200000$。

    题解

    其实也是个整体二分模板题。

    如果只有一组询问,我们可以二分答案,判断的话就扫一遍询问区间,看有几个数比这个答案小,就可以确定这个答案是区间第几小了。

    对于多组询问的话,我们可以把所有操作扔到一起二分。

    具体是什么意思呢?

    举个例子,给出一个数 $x$,在二分答案 $mid$ 时,它只会对所有大于 $mid$ 的答案造成影响。也就是说它跟询问一样,可以被二分。

    下面说说具体操作:

    我们按输入的顺序,依次模拟当前操作区间的操作。(在模拟当前区间的所有操作之前,把之前二分时记录的信息都清空,即假装之前啥操作也没做过)

    如果这个操作是赋值操作(在这题就是一开始给数组赋值),

      如果赋的数小于等于 $mid$,设这个数为 $x$,把它在树状数组/线段树的第 $x$ 位 $+1$,并把这个操作扔到左递归区间(即 $[l,mid]$ 答案区间);

      如果赋的数大于 $mid$,直接把这个操作扔到右递归区间(即 $(mid,r]$ 区间)。

    如果这个操作是询问操作,

      先用树状数组/线段树对其询问区间求和(这就是之前为什么说在问题比较简单时 才适合整体二分,你得确保能再用个数据结构维护),也就是查其询问区间当前有多少个小于等于 $mid$ 的数,这里设有 $x$ 个。

      如果 $x$ 小于该查询的 $k$(即这个查询问的是区间第 $k$ 小),就把该询问的 $k$ 减去 $x$(左递归区间有足够的数小于 $mid$,这个询问的答案显然在右递归区间,于是减去左区间的数的影响),并把这个操作扔到右递归区间;

      如果 $x$ 大于等于该查询的 $k$,直接把这个操作扔到左递归区间。

    如果二分答案的区间 $[l,r]$ 递归到了 $l=r$,就把扔到这个递归区间的所有询问操作的答案都赋为 $l$ 或 $r$,回溯。

    $Q1:$ 为什么对于赋值操作,赋的数小于等于 $mid$ 时,要把操作扔到左递归区间?

    $A1:$ 因为我们在询问操作中,把要递归到右子区间的询问 减掉了小于等于 $mid$ 的数对询问的影响,也就是说这个赋值操作以后不用管递归到右子区间的询问操作了,于是扔到左子区间即可。

    $Q2:$ 那赋的数大于 $mid$ 时,要把操作扔到右递归区间?

    $A2:$ 因为这个数不会对询问答案在左子区间的询问造成影响,它不会参与这些询问的排名。

    其它证明都比较显然了吧(其实是我没时间写)

    对于这题,如果不离散化的话,只能用权值线段树;如果离散化的话,树状数组和权值线段树就都可以用了。

    注意一个事情,一开始就要把所有操作的数改为离散化后的数,不要在整体二分里再对每个数套 $map$ 查询,因为整体二分的时间复杂度顶多做到 $O(n imes log^2(n))$,再套个 $map$ 的 $log$ 可能会爆炸。

     1 #include<bits/stdc++.h>
     2 #define rep(i,x,y) for(int i=x;i<=y;++i)
     3 #define dwn(i,x,y) for(int i=x;i>=y;--i)
     4 #define ll long long
     5 #define N 400001
     6 using namespace std;
     7 inline int read(){
     8     int x=0; bool f=1; char c=getchar();
     9     for(;!isdigit(c);c=getchar()) if(c=='-') f=0;
    10     for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^'0');
    11     if(f) return x;
    12     return 0-x;
    13 }
    14 int n,m,a[N],b[N],ans[N],cnt;
    15 map<int,int>mp; int lsh[N];
    16 struct query{int x,l,r,k;}q[N],L[N],R[N];
    17 namespace Fwk{
    18     int fwk[N];
    19     inline int lowbit(int x){return x&(-x);}
    20     inline void add(int x,int v){
    21         for(;x<=n;x+=lowbit(x)) fwk[x]+=v;
    22     }
    23     inline int query(int x){
    24         int ret=0;
    25         for(;x>0;x-=lowbit(x)) ret+=fwk[x];
    26         return ret;
    27     }
    28 };
    29 void solve(int ql,int qr,int l,int r){
    30     //printf("solve:%d %d %d %d
    ",ql,qr,l,r);
    31     if(l==r){
    32         rep(i,ql,qr) if(q[i].x) ans[q[i].x]=l;
    33         return;
    34     }
    35     int mid=(l+r)>>1,cntL=0,cntR=0,tmp;
    36     rep(i,ql,qr){
    37         //cout<<"faq:"<<Fwk::query(2)<<' '<<Fwk::query(0)<<endl;
    38         if(!q[i].x){
    39             if(q[i].k<=mid) L[++cntL]=q[i], Fwk::add(q[i].l,1);
    40             else R[++cntR]=q[i];
    41             //printf("1:%d %d %d
    ",q[i].l,q[i].k,mid);
    42         }
    43         else{
    44             tmp=Fwk::query(q[i].r)-Fwk::query(q[i].l-1);
    45             if(tmp<q[i].k) q[i].k-=tmp, R[++cntR]=q[i];
    46             else L[++cntL]=q[i];
    47             //printf("2:%d %d %d %d %d
    ",q[i].x,q[i].l,q[i].r,q[i].k,tmp);
    48         }
    49     }
    50     rep(i,1,cntL){
    51         q[ql-1+i]=L[i];
    52         if(!L[i].x) Fwk::add(L[i].l,-1);
    53     }
    54     rep(i,1,cntR) q[ql-1+cntL+i]=R[i];
    55     solve(ql,ql-1+cntL,l,mid), solve(ql-1+cntL+1,qr,mid+1,r);
    56 }
    57 int main(){
    58     //freopen("luogu3834.in","r",stdin);
    59     //freopen("luogu3834.out","w",stdout); 
    60     n=read(),m=read();
    61     rep(i,1,n) b[i]=read(), q[i]=(query){0,i,i,b[i]};
    62     sort(b+1,b+n+1);
    63     mp[b[1]]=++cnt, lsh[cnt]=b[1];
    64     rep(i,2,n) if(b[i]!=b[i-1]) mp[b[i]]=++cnt, lsh[cnt]=b[i];
    65     int l,r,k;
    66     rep(i,1,m) l=read(),r=read(),k=read(), q[n+i]=(query){i,l,r,k};
    67     rep(i,1,n) q[i].k=mp[q[i].k];
    68     solve(1,n+m,1,cnt);
    69     rep(i,1,m) printf("%d
    ",lsh[ans[i]]);
    70     return 0;
    71 }
    View Code

    Dynamic Rankings(zoj2112)

    询问动态区间第 $k$ 小。

    题解

    我们发现,既然可以把所有操作都混到一块,为什么不能在询问中间插入一些修改操作呢?

    修改操作可以看成减一个数,再加一个数。因此把每个修改操作拆成减去原来的数、加上新的数即可。

    注意,对于减去的数,要按它的绝对值进行整体二分的递归判断。因为之前肯定加过这个数,之前那个数对 $mid$ 有影响(给线段树对应数位加 $1$ 什么的),这个数就对 $mid$ 有影响(比如给线段树对应数位 $-1$,去掉之前那个数的影响),也就是说这个数实际上就是消去之前加上的那个数的影响,之前那个数在哪,这个数就得在哪。(可以用反证法证明这个性质,想想如果这对加减操作不在同一个操作区间,减法后面的操作会发生什么)

    后记

    其实大部分整体二分的题目都完全可以用纯数据结构(比如主席树)代替,写整体二分只是为了减小常数。

    了解一下,会写就好了。

  • 相关阅读:
    P31 整体更新或新增 PUT
    P30 整体更新/替换资源 PUT
    P29 自定义错误信息和错误报告
    python: openpyxl带格式复制excel
    Android控件与布局——基础控件RadioButton
    EditText 使用详解
    Linux内核内存检测工具KASAN
    ISP基础(10)-Gamma校正及其实现
    ISP基础(08)-动态范围压缩
    ISP基础(07)-自动曝光
  • 原文地址:https://www.cnblogs.com/scx2015noip-as-php/p/whole_dichotomy.html
Copyright © 2011-2022 走看看