• 【暖*墟】 #洛谷省选网课# 7.30量产数据结构


    量产数据结构

    概念:给一个序列,用树维护信息。

    • 偏序类
    • 树分治类
    • 小Z的袜子类

    一. 偏序类

    1.偏序的概念

    设 A 是一个非空集,P 是 A 上的一个关系,

    若关系P是自反的、反对称的、和传递的,则称P是集合A上的偏序关系。

    即P适合下列条件:

    • (1)对任意的 a∈A , (a,a)∈P ;
    • (2)若(a,b)∈P 且(b,a)∈P ,则  a=b ;
    • (3)若(a,b)∈P , (b,c)∈P ,则(a,c)∈P ,
    •   则称 P 是 A 上的一个偏序关系。

    带偏序关系的 集合 A 称为 偏序集或半序集 。

    若P是A上的一个偏序关系,我们用 a≤b 来表示(a,b)∈P。

    举如下例子说明偏序关系:

    1、实数集上的小于等于关系是一个偏序关系。

    2、设 S 是集合,P(S)是 S 的所有子集构成的集合,

          定义 P(S)中两个元素 A≤B 当且仅当 A 是 B 的子集,

          即 A 包含于 B,则P(S)在这个关系下成为偏序集。

    3、设 N 是正整数集,定义 m ≤ n 当且仅当m能整除n,不难验证这是一个偏序关系。

          注意它不同于N上的自然序关系。 偏序是在集合 P 上的二元关系(≤),

          它是自反的反对称的、和传递的,就是说,对于所有 P 中的 a, b 和 c,有着:

    a ≤ a (自反性); a ≤ b 且 b ≤ a 则 a = b (反对称性); a ≤ b 且 b ≤ c 则 a ≤ c (传递性)。

    【定义简单化】

    所谓偏序就是当你知道元素A,B,C时,元素A<B,且A<C,

    但是B和C之间的关系却无法比较的现象。在ACM中,

    此类的题目通常会告诉我们n个数组,每个元素的各属性对应于不同数组的同一位置的值 。

    询问通常是回答比这元素小的有几个,或者是LIS这样的dp问题。

    ( LIS:最长上升(递增)子序列。)

    通常有以下方法:排序,数据结构(树状数组,线段树,平衡树),cdq分治,分块。

    2.偏序类---量产数据结构

    即每次对满足多维(某维)的一个限制的所有数进行操作。

    多维(某维)的限制:每个点 i 有 ai,bi,ci...不同的值。

    操作具体:每次对每个值满足的某区间进行一次修改操作。

    例如,对满足 l1<=ai<=r1 , l2<=bi<=r2 ...的 i 进行一次修改操作。

    3.具体实现和维护

    (1) Range Tree

    范围树:狭义上的树套树.

    • 能在O( logn^d )的复杂度内进行一次d维偏序的空间查询
    • 能在O( logn^d )的复杂度内进行一次d维偏序的单点修改
    • 空间为O( nlogn^(d-1) ),可以优化到O( n(logn/loglogn)^(d-1) )

    如果要维护d维,出于方便,设每维的值大小是v的一个偏序。

    *高维树状数组*

    本质就是树状数组的嵌套。时间复杂度O( logv^d ),空间复杂度O( v^d )。

    可以预先高维离散化来优化。时间复杂度O( logv^d ),空间复杂度O( nlogv^d )。

    //普通树状数组写法
     
    inline void add(int x,int y){ //元素修改(增加)
        for(int i=x;i<=n;i+=lowbit(i))
            t[i]+=y;
    }
    //二维树状数组+偏序写法
     
    inline void add(int x,int y,int z){ //空间修改(增加)
        for(int i=x;i<=n;i+=lowbit(i))
            for(int j=y;j<=n;j+=lowbit(j))
                t[i][j]+=z;
    }

    其实高维树状数组一般只有二维。

    二维离散化含义及用法:先访问一遍,选择离散化(二维),用类似hash筛选有用的点。

    *树状数组套树*

    优势:好写,常数。

    劣势:只能维护支持减法的信息,如果不能满足减法则基本上不能使用。

    1)套平衡树:时间复杂度O( logn^2 ),空间复杂度O( nlogn )。

    优势:空间。

    劣势:平衡树没有简单的可以在多个平衡树上二分的方法,

    区间kth这种询问会多一个log(其实可以不多log的)。

    2)套线段树:时间复杂度O( lognlogv ),空间复杂度O( nlognlogv )。

    优势:可以简单地在多个线段树上二分。  劣势:空间。

    *线段树套树*

    套平衡树:时间复杂度O( logn^2 ),空间复杂度O( nlogn )。

    套Trie:时间复杂度O( lognlogv ),空间复杂度O( nlognlogv )。

    优点:可以维护不支持减法的信息。

    缺点:相对难写,慢,空间大。

    线段树原理  查询一个区间,通过分治把它分成了若干个区间,

    让区间维护自己的信息。只需要支持信息能高调合并。

    线段树模板函数  insert是插入函数,erase是删除函数。

    find是查询区间和,rank是查找小于x的数的个数。

    普通线段树(modify函数)

    void Modify(int x,int y,int pos,long long val){
        int mid=x+y>>1;
        if(x==y){
            this->val+=val; return ;
        }
        if(pos<=mid) ls->Modify(x,mid,pos,val);
        else rs->Modify(mid+1,y,pos,val);
        this->val=GCD(ls->val,rs->val);
    }

    线段树套平衡树(要写之前的线段树模板,运用wblt)

    先构造出线段树,每个线段树除了记录左边界和右边界之外,

    还储存了一棵平衡树(当然实际上只需要储存根节点),对应着这一个区间的所有数。

    每次修改区间L~R时,需要将所有包含L~R的线段树节点的平衡树都修正。

    操作其实没有什么难点,和普通线段树一样分块处理即可,效率为O(log2(n))。

    但是查询区间第k大不能像普通线段树一样,必须用二分枚举答案mid,

    然后查询mid的排名,如果排名<=k就增大mid,否则减小mid。

    假设二分跨度为t,则效率为O(log2(n)∗log2(t))。

    ps:树套树常数较大,请谨慎使用。

    模板:以BZOJ3196为例,这里使用的是线段树Treap(二叉搜索树)。

    #include<cstdio>
    #include<cstdlib>
    #include<algorithm>
    using namespace std;
    const int maxn=50000,maxt=1700000,MAXINT=((1<<30)-1)*2+1;
     
    int n,te,a[maxn+5];
     
    //============================================================
    struct Node{
        Node *son[2];
        int val,fix,si,w;
        int cmp(int k) {if (k==val) return -1;if (k<val) return 0; else return 1;}
        void Pushup() {si=son[0]->si+w+son[1]->si;}
    };
     
    typedef Node* P_node;
    Node tem[maxt+5];
    P_node null=tem,len=null;
    P_node newNode(int k){
        len++;len->son[0]=len->son[1]=null;
        len->si=len->w=1;len->val=k;len->fix=rand();
        return len;
    }
     
    void Rotate(P_node &p,int d){
        P_node t=p->son[d^1];p->son[d^1]=t->son[d];t->son[d]=p;
        p->Pushup();t->Pushup();p=t;
    }
     
    void Insert(P_node &p,int k){
        if (p==null) {p=newNode(k);return;}
        int d=p->cmp(k);
        if (d==-1) p->w++; else{
            Insert(p->son[d],k);
            if (p->son[d]->fix>p->fix) Rotate(p,d^1);
        }
        p->Pushup();
    }
     
    void Delete(P_node &p,int k){
        if (p==null) return;
        int d=p->cmp(k);
        if (d==-1){
            if (p->w>1) p->w--; else
            if (p->son[0]==null) p=p->son[1]; else
            if (p->son[1]==null) p=p->son[0]; else{
                int d;if (p->son[0]->fix>p->son[1]->fix) d=0; else d=1;
                Rotate(p,d);if (p==null) return;Delete(p->son[d],k);
            }
            if (p==null) return;
        } else Delete(p->son[d],k);
        p->Pushup();
    }
     
    int getrank(P_node p,int k){ //对于不存在的k,排名是k后继的排名
        if (p==null) return 1;
        int d=p->cmp(k);
        if (d==-1) return p->son[0]->si+1; else
        if (d==0) return getrank(p->son[0],k); else
        return getrank(p->son[1],k)+p->son[0]->si+p->w;
    }
     
    int getpre(P_node p,int k){
        if (p==null) return -MAXINT;
        int d=p->cmp(k);
        if (d==1) return max(getpre(p->son[1],k),p->val); else
        return getpre(p->son[0],k);
    }
     
    int getsuf(P_node p,int k){
        if (p==null) return MAXINT;
        int d=p->cmp(k);
        if (d==0) return min(getsuf(p->son[0],k),p->val); else
        return getsuf(p->son[1],k);
    } //以上为Treap
    //============================================================
     
    struct SegmentTree{
        int l[4*maxn+5],r[4*maxn+5];P_node ro[4*maxn+5]; //只保留根指针
        
        void Build(int p,int L,int R){
            l[p]=L;r[p]=R;ro[p]=null;
            if (L==R) return;
            int mid=L+(R-L>>1);
            Build(p<<1,L,mid);Build(p<<1|1,mid+1,R);
        }
     
        void Seg_Insert(int p,int L,int k){
            if (L<l[p]||r[p]<L) return;
            if (l[p]<=L&&L<=r[p]) Insert(ro[p],k);
            //这里不是L<=l[p]&&r[p]<=L,因为所有包含L的节点都要插入k
            if (l[p]==r[p]) return;
            Seg_Insert(p<<1,L,k);Seg_Insert(p<<1|1,L,k);
        }
        
        void Seg_Delete(int p,int L,int k){
            if (L<l[p]||r[p]<L) return;
            if (l[p]<=L&&L<=r[p]) Delete(ro[p],k);
            //同理
            if (l[p]==r[p]) return;
            Seg_Delete(p<<1,L,k);Seg_Delete(p<<1|1,L,k);
        }
     
        int Seg_rank(int p,int L,int R,int k) //这个函数返回真正的答案-1,防止重复
        {
            if (R<l[p]||r[p]<L) return 0;
            if (L<=l[p]&&r[p]<=R) return getrank(ro[p],k)-1;
            //这里是普通线段树查询,所以是L<=l[p]&&r[p]<=R
            return Seg_rank(p<<1,L,R,k)+Seg_rank(p<<1|1,L,R,k);
        }
     
        int Seg_kth(int l,int r,int k){
            int L=0,R=1e8;
            while (L<=R) //二分
            {
                int mid=L+(R-L>>1),rk=Seg_rank(1,l,r,mid)+1;
                if (rk<=k) L=mid+1; else R=mid-1;
            }
            return R;
        }
     
        int Seg_pre(int p,int L,int R,int k){
            if (R<l[p]||r[p]<L) return -MAXINT;
            if (L<=l[p]&&r[p]<=R) return getpre(ro[p],k);
            return max(Seg_pre(p<<1,L,R,k),Seg_pre(p<<1|1,L,R,k));
        }
     
        int Seg_suf(int p,int L,int R,int k){
            if (R<l[p]||r[p]<L) return MAXINT;
            if (L<=l[p]&&r[p]<=R) return getsuf(ro[p],k);
            return min(Seg_suf(p<<1,L,R,k),Seg_suf(p<<1|1,L,R,k));
        }
    };
    SegmentTree tr; //以上为线段树
    //============================================================
     
    bool Eoln(char ch) {return ch==10||ch==13||ch==EOF;}
    int readi(int &x){
        int tot=0,f=1;char ch=getchar(),lst=' ';
        while ('9'<ch||ch<'0') {if (ch==EOF) return EOF;lst=ch;ch=getchar();}
        if (lst=='-') f=-f;
        while ('0'<=ch&&ch<='9') tot=tot*10+ch-48,ch=getchar();
        x=tot*f;
        return Eoln(ch);
    }
     
    void LNR(P_node ro){
        if (ro==null) return;
        LNR(ro->son[0]);for (int i=1;i<=ro->w;i++) printf("%d
    ",ro->val);LNR(ro->son[1]);
    }
     
    int main(){
        freopen("STBST.in","r",stdin);
        freopen("STBST.out","w",stdout);
        readi(n);readi(te);tr.Build(1,1,n);
        for (int i=1;i<=n;i++) readi(a[i]),tr.Seg_Insert(1,i,a[i]);
        while (te--){
            int td,x,y,z;readi(td);readi(x);readi(y);
            switch (td){
                case 1:readi(z);printf("%d
    ",tr.Seg_rank(1,x,y,z)+1);break;
                case 2:readi(z);printf("%d
    ",tr.Seg_kth(x,y,z));break;
                case 3:tr.Seg_Delete(1,x,a[x]);tr.Seg_Insert(1,x,y);a[x]=y;break;
                case 4:readi(z);printf("%d
    ",tr.Seg_pre(1,x,y,z));break;
                case 5:readi(z);printf("%d
    ",tr.Seg_suf(1,x,y,z));break;
            }
        }
        return 0;
    }
    View Code

    *平衡树套树*

    套平衡树:时间复杂度O( logn^2 ),空间复杂度O( nlogn )。

    套Trie:时间复杂度O( lognlogv ),空间复杂度O( nlognlogv )。

    优点:可以在线支持第一维插入的问题。

    缺点:难写,更慢,常数更大。

    *树套OVT*

    套一个排序后的vector,OVT == Ordered Vector Tree。

    优点:好写,空间。缺点:复杂度。

    【例题】Luogu3380 二逼平衡树

    题意

    您需要写一种数据结构(可参考题目标题)来维护一个有序数列,

    其中需要提供以下操作:1.查询k在区间内的排名。

    2.查询区间内排名为k的值。 3.修改某一位值上的数值。

    4.查询k在区间内的前驱。5.查询k在区间内的后继。n,m<=5e4

    Solution1

    题目翻译:发现本质就是个带单点修改的区间kth,区间rank。

    可以用线段树套平衡树维护,时间O( logn^3 )。

    (kth:区间第k大的数;可以用rank函数求出。)

    -->维护不支持差分,不支持修改。

    -->二分答案:check函数 查找判断区间中mid的数值是否小于等于k。

    Solution2

    发现区间kth可以在多个Trie上一起二分来维护。

    ( 0/1trie + 线段树 = 权值线段树 )

    可以用线段树套Trie维护,时间O( logn^2 )。

    -->找两个前缀来表示区间,在这两个前缀的trie上二分。

    Solution3

    发现区间kth可以支持减法,可以用树状数组套Trie维护。

    时间O( logn^2 ),很好写,常数也小。

    Solution4(特殊数据结构&&只针对一个问题)

    通过读论文可以发现可以用动态划分树维护。

    时间O( (logn/loglogn)^2 )。

                                                     ——时间划过风的轨迹,那个少年,还在等你。

  • 相关阅读:
    silverlight 视频
    Win7/Vista下安装SQL Server 2005/2008后,进行附加数据库错误的解决
    IIS篇
    ARCGIS 定位篇
    sqlserver 2008 修改表结构不能保存
    更新silverlight 后 无法启动调试 未安装silverlight developer 运行时解决办法
    2点经纬度计算相对方位
    VS2010中文旗舰版在WIN7 64位操作系统下安装
    self = [super init]
    NSXMLParser XML 解析 解压
  • 原文地址:https://www.cnblogs.com/FloraLOVERyuuji/p/9391128.html
Copyright © 2020-2023  润新知