线段树动态开点

动态开点

为了降低权值线段树的空间复杂度,可以不直接建出整棵线段树的结构,而是在最初只建立一个根节点,当需要访问某棵为建立的子树的时候,再建立代表这个子树的节点。
动态开点的线段树用变量记录左右节点的编号。
值域为1-n的动态开点线段树在m次单点修改后,节点规模为O(mlogn)

例题:

P1908 逆序对
(这题n最大1e9,正常应该用离散化做,但是有了动态开点不怕)

涉及单点修改、查询区间和

code:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
typedef long long ll;
using namespace std;
const int maxm=1e7+5;
struct Node{
    int lc,rc,sum;//lc,rc记录左右子节点编号
}tr[maxm];
int cnt;
int build(){//返回新节点的编号
    cnt++;
    tr[cnt].lc=tr[cnt].rc=tr[cnt].sum=0;
    return cnt;
}
void init(){//定义1为根节点
    cnt=1;
    tr[1].lc=tr[1].rc=tr[1].sum=0;
}
void pushup(int node){
    tr[node].sum=tr[tr[node].lc].sum+tr[tr[node].rc].sum;
}
void update(int x,int v,int l,int r,int node){
    if(l==r){
        tr[node].sum+=v;
        return ;
    }
    int mid=(l+r)/2;
    if(x<=mid){
        if(!tr[node].lc)tr[node].lc=build();//如果左边是空的就建立
        update(x,v,l,mid,tr[node].lc);
    }else{
        if(!tr[node].rc)tr[node].rc=build();//如果右边是空的就建立
        update(x,v,mid+1,r,tr[node].rc);
    }
    pushup(node);
}
int ask(int st,int ed,int l,int r,int node){
    if(st<=l&&ed>=r){
        return tr[node].sum;
    }
    int mid=(l+r)/2;
    int ans=0;
    if(st<=mid){
        if(tr[node].lc)ans+=ask(st,ed,l,mid,tr[node].lc);
    }
    if(ed>=mid){
        if(tr[node].rc)ans+=ask(st,ed,mid+1,r,tr[node].rc);
    }
    return ans;
}
int main(){
    init();
    int n;
    scanf("%d",&n);
    ll ans=0;
    for(int i=0;i<n;i++){
        int t;
        scanf("%d",&t);
        ans+=ask(t+1,1e9,1,1e9,1);
        update(t,1,1,1e9,1);
    }
    printf("%lld",ans);
    return 0;
}

线段树合并

若多棵动态开点线段树值域都为1-n,显然所有树对于子区间的划分是相同的。
现在要把所有树的信息整合在一起,即线段树合并.

合并流程:
选择两棵线段树,用p,q指向两棵树的根节点,向下递归合并,(递归方向必须相同,即p,q代表相同区间)。
如果过程中p,q某一为空,则以非空的作为合并之后的节点。
否则继续递归合并两棵左子树和两棵右子树,回溯时删除其中一个节点(如删除q),以另一节点作为合并之后的节点(如p),同时自底向上统计合并后的信息。

code:

维护区间最值

const int maxm=1e7+5;
struct Node{
    int lc,rc,ma;
}tr[maxm];
int merged(int p,int q,int l,int r){
    if(!p)return q;//p、q中有一个为空
    if(!q)return p;
    int mid=(l+r)/2;
    tr[p].lc=merged(tr[p].lc,tr[q].lc,l,mid);
    tr[p].rc=merged(tr[p].rc,tr[q].rc,mid+1,r);
    tr[p].ma=max(tr[tr[p].lc].ma,tr[tr[p].rc].ma);
    return p;
}

因为发生递归必有一节点被删除,因此函数执行次数不会超过节点总数+1,因此复杂度为O(mlogn),与单点操作复杂度相同


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值