码迷,mamicode.com
首页 > 其他好文 > 详细

动态开点线段树

时间:2020-07-19 18:01:19      阅读:66      评论:0      收藏:0      [点我收藏+]

标签:编号   log   自己   main   int   关节   排序   例题   src   

动态开点线段树

前置芝士

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

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

怎么办呢?

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

概念

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

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

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

技术图片

作用

? 一般有两种:

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

代码

更新
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\n",ans);
    return 0;
}

有问题请评论

动态开点线段树

标签:编号   log   自己   main   int   关节   排序   例题   src   

原文地址:https://www.cnblogs.com/jasony/p/13339512.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!