线段树分裂与合并 学习笔记

线段树是很基本的一种数据结构,也是一种很强大的数据结构,它的衍生有权值线段树,可持久化线段树(主席树).还有现在要介绍的分裂合并版本的线段树.为了方便,下面的文章都用合并树来代替这一长串的称呼.
这种线段树的一般套路都是维护权值,而不是维护序列.一般来说,合并树的基本操作就以下两种:
add:

void add(int &now,int l,int r,int p,int v){
   
    if(!now) now = ++tot;
    tree[now].sum += v;
    if(l >= r) return;
    int mid = l + r >> 1;
    if(p <= mid) add(lson(now),l,mid,p,v);
    else add(rson(now),mid+1,r,p,v);
}

merge:

int merge(int x,int y){
   
    if(!x||!y) return x+y;
    tree[x].sum += tree[y].sum;
    lson(x) = merge(lson(x),lson(y));
    rson(x) = merge(rson(x),rson(y));
    return x;
}

要注意的是,为了节省空间等原因,我们不再用2×i,2×i+1的方式记录左右儿子,而是新开两个指针变量来记录左右儿子.而且也不在线段树中储存左右区间范围,而是在传递的时候增加两个变量记录区间范围.这里的add操作和主席树的add操作有相似之处,merge操作和fhq_treap有相似之处.如果学过这两种数据结构应该比较好理解这两个代码.没学过看一下估计也理解了.
那有了这两种操作之后怎么去写这类型的题目呢?下面给几道例题大伙感受一下.
Promotion Counting P
我们先把1看做根节点.如果只求ans[1]很好做,我们只要开一个线段树或者树状数组,储存所有的子树中的权值,然后求a[1]+1-maxvalue有多少个数就ok了.
那么要求全部的ans数组怎么操作呢?我们考虑一开始为每一个节点都开一颗合并树.因为是动态开点,所以一棵树的空间复杂度是O(logn)的.然后从叶子节点开始,一层一层往上合并,就能跟求ans[1]一样求出每一个节点的答案.
代码:

#define LL long long
#define pq priority_queue
#define ULL unsigned long long
#define pb push_back
#define mem(a,x) memset(a,x,sizeof a)
#define pii pair<int,int>
#define fir(i,a,b) for(int i=a;i<=(int)b;++i)
#define afir(i,a,b) for(int i=(int)a;i>=b;--i)
#define ft first
#define vi vector<int>
#define sd second
#define ALL(a) a.begin(),a.end()
#define bug puts("-------")
#define mpr(a,b) make_pair(a,b)
#define lson(i) tree[i].l
#define rson(i) tree[i].r
#include <bits/stdc++.h>

using namespace std;
const int N = 2e5+10;

inline void read(int &a){
   
    int x = 0,f=1;char ch = getchar();
    while(ch<'0'||ch>'9'){
   if(ch=='-')f=-1;ch=getchar();}
    while(ch<='9'&&ch>='0'){
   x=x*10+ch-'0';ch=getchar();}
    a = x*f;
}
struct Tree{
   
    int l,r,sum;
}tree[N*20];
int tn,n,m,ct,nxt[N],head[N],to[N],w[N],ans[N];
vi all;
void addedge(int x,int y){
   
    nxt[++ct] = head[x];head[x] = ct;to[ct] = y;
}
int getpos(int x){
   
    return lower_bound(ALL(all),x) - all.begin();
}
int tot,root[N];

void add(int &now,int l,int r,int p,int v){
   
    if(!now) now = ++tot;
    tree[now].sum += v;
    if(l >= r) return;
    int mid = l + r >> 1;
    if(p <= mid) add(lson(now),l,mid,p,v);
    else add(rson(now),mid+1,r,p,v);
}

void pre(int u,int fa){
   
    root[u] = ++tot;
    add(root[u],1,tn,getpos(w[u]),1);
    for(int i = head[u];i;i = nxt[i]){
   
        int y = to[i];
        if(y == fa) continue;
        pre(y,u);
    }
}
int merge(int x,int y){
   
    if(!x||!y) return x+y;
    tree[x].sum += tree[y].sum;
    lson(x) = merge(lson(x),lson(y));
    rson(x) = merge(rson(x),rson(y));
    return x;
}
int query(int &now,int l,int r,int ll,int rr){
   
    if(ll > rr) 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值