• 动态开点线段树


    动态开点线段树

    前置芝士

    众所周知,普通线段树空间复杂度是 (O(n*4))

    所以当n很大的时候,如果正常的去建一颗线段树,开4倍n空间显然会炸内存

    怎么办呢?

    这个时候,动态开点线段树出现了。

    概念

    ​ 动态开点线段树是一类特殊的线段树,与普通的线段树不同的是,每一个节点的左右儿子不是该点编号的两倍和两倍加一,而是现加出来的。

    ​ 简单的说,就是建立一棵“残疾”的线段树,上面只有询问过的相关节点。

    ​ 大概长这样(借的图,自己画的太丑了),理解即可,写出来的可能不长这样

    img

    作用

    ​ 一般有两种:

    • 节约空间,所以在需要时再建儿子。
    • 运用主席树的时候(此文章不会提及)。

    代码

    更新
    inline void push_up(int o){
        sum[o]=sum[ls[o]]+sum[rs[o]];
    }
    

    ls[o],rs[o]代表lson和rson,以下都一样

    插入
    inline void update(int &o,int l,int r,int pos,int val){	//o为当前树的编号 l r为区间 pos为插入位置 val为插入大小
        if(!o) o=++cnt;//开点
        if(l==r){
            sum[o]+=val;
            return ;
        }
        int mid=(l+r)>>1;
        if(x<=mid) update(ls[o],l,mid,pos,val);
        else update(rs[o],mid+1,r,pos,val);
        push_up(o);//更新
    }
    
    查询
    inline int ask(int o,int l,int r,int L,int R){
        if(!o) return 0;//没有这个点,直接返回0
        if(L<=l&&r<=R) return sum[o];
        int val,mid=(l+r)>>1;
        if(L<=mid) val+=ask(ls[o],l,mid,L,R);
        if(R>mid) val+=ask(rs[o],mid+1,r,L,R);
        return val;
    }
    

    和普通线段树一样,可维护的东西很多,在这里就不一一枚举了,读者自行理解。

    提示
    1. 这个参数变量pos前面的取址符&是不能省略的,因为这个编号是不随递归修改的定值
    2. 时间复杂度 (O(dfrac{q}{logn})~~~~q) 为查询次数,(n) 为值域

    例题

    P1908 逆序对
    题意

    就是求一个序列的逆序对个数

    思路

    相信大家都做过这题,所以就不说思路了,只是把归并排序改为了动态开点线段树

    代码

    有注释的

    #include <bits/stdc++.h>
    using namespace std;
    
    #define ll long long
    #define lson l,mid,tree[rt].l
    #define rson mid+1,r,tree[rt].r
    #define ls tree[rt].l
    #define rs tree[rt].r
    
    const int mac=5e5+10;
    const int inf=1e9+5;
    
    struct node
    {
        int l,r,sum;
    }tree[mac*32];
    int sz=1;//动态分配的点的最大编号
    
    ll query(int l,int r,int rt,int L,int R)
    {
        ll ans=0;
        if (l>=L && r<=R){
            return tree[rt].sum;
        }
        int mid=(l+r)>>1;
        if (mid>=L) ans+=query(lson,L,R);
        if (mid<R) ans+=query(rson,L,R);
        return ans;
    }
    
    void update(int l,int r,int &rt,int pos)
    {
        if (!rt) rt = ++sz;//如果说这个节点不存在,我们就将节点数+1,当前节点为最大最大节点,和主席树有点类似,而这也是动态开点的核心
        if (l==r) {
            tree[rt].sum++;
            return;
        }
        int mid=(l+r)>>1;
        if (mid>=pos) update(lson,pos);//注意这里传进去的rt是左儿子的编号
        else update(rson,pos);
        tree[rt].sum=tree[ls].sum+tree[rs].sum;
    }
    
    int main()
    {
        int n;
        scanf ("%d",&n);
        ll ans=0;
        int root=1;
        for (int i=1; i<=n; i++){
            int x;
            scanf ("%d",&x);
            ans+=query(1,inf,1,x+1,inf);
            update(1,inf,root,x);
        }
        printf ("%lld
    ",ans);
        return 0;
    }
    

    有问题请评论

  • 相关阅读:
    网上邻居无法打开的问题 客户端无法连接打印机问题
    今天一天下午到晚上都在研究如何刷手机,要是被领导知道我帮同学在刷手机系统,非开除我不可。还是贴出来,以后少走弯路吧
    “屏幕保护程序”没有出现“在恢复时使用密码保护”的解决方法
    NOD 32客户端安装时出现的问题
    [模板]二叉树的前、中、后序遍历
    [算法]浅谈求n范围以内的质数(素数)
    [模板]二叉搜索树
    [OI]Noip 2018 题解&总结(普及)
    Centos7.2安装git(源码安装)
    Centos7.2安装maven
  • 原文地址:https://www.cnblogs.com/jasony/p/13339512.html
Copyright © 2020-2023  润新知