权值线段树&&线段树合并

权值线段树

所谓权值线段树,就是一种维护值而非下标的线段树,我个人倾向于称呼它为值域线段树。

举个栗子:对于一个给定的数组,普通线段树可以维护某个子数组中数的和,而权值线段树可以维护某个区间内数组元素出现的次数。

在实现上,由于值域范围通常较大,权值线段树会采用离散化或动态开点的策略优化空间。

更新操作:

更新的时候,我们向线段树中插入一个值v,那么所有包含v的区间值都需要+1。(每个节点维护对应区间中出现了多少个数)

 int update (long long v,long long l,long long r,int pos) { // 插入v,当前区间为[l,r]。
      if (!pos) pos=++tot_node;
      // 如果该节点不存在,则新建节点。
      if (l<=v&&v<=r) {
          // 如果当前区间包含插入值。
          tree[pos].val++;
          // 出现次数+1。
          if (l==r) return pos;
          // 如果递归到叶子节点,退出。
     }
     long long mid=(l+r)>>1;
     if (v<=mid) tree[pos].ls=update(v,l,mid,tree[pos].ls);
     else tree[pos].rs=update(v,mid+1,r,tree[pos].rs);
     // 判断插入值是在当前区间的哪一半。
     pushup(pos);
     // 回溯。
     return pos;
 }

查询操作:

查询操作类似二叉树。

 long long query (long long l,long long r,long long L,long long R,int pos) { // 查询区间[L,R]中数字的数量,当前区间为[l,r]。
     if (!pos) return 0;
     // 如果该节点不存在,必然没有到达过。
     if (L<=l&&r<=R) {
         // 如果当前区间属于查询区间。
         return tree[pos].val;
         // 直接返回区间中数字的数量。
     }
     long long mid=(l+r)>>1,ans=0;
     if (L<=mid) ans+=query(l,mid,L,R,tree[pos].ls);
     if (mid<R) ans+=query(mid+1,r,L,R,tree[pos].rs);
     // 统计区间和。
     return ans;
 }

练习题:

作为练习模板,可以考虑逆序对。大体思路是每次查询a[i]+1~n的元素个数。

线段树合并

所谓线段树合并,就是通过将合并两颗线段树获得信息,其正确性由线段树的稳定结构保证。

线段树合并通常是一个自底向上的过程,在深搜的途中将子节点的树合并到父节点上,从而实现对父节点值的统计。

线段树合并的复杂度是\(nlogn\),比启发式合并少一个\(logn\)。

不难发现,如果按照线段树合并的原始思想直接在每一个需要遍历的节点上都单独建立一个线段树肯定会爆空间。在这里可以使用被称为“回收内存”的方法:由于在合并之后子节点的信息已经归入父节点,所以子节点没有用处,那么可以将其所有节点回收丢入一个内存池,往后更新的时候可以从内存池里取节点而非新建节点。

回收内存:

 inline int newId () {
     if (pool_top) return mempool[pool_top--];
     return ++tot_node;
 }
 inline void killId (int &x) {
     mempool[++pool_top]=x;
     tree[x].ls=tree[x].rs=tree[x].val=0;
     x=0;
 }

合并操作:

合并操作与左偏树的合并有一点像,就是递归合并每一个节点。

 int merge (int l,int r,int x,int y) {
     if (!x||!y) return x+y;
     int now=newId(),mid=(l+r)>>1;
     if (l==r) {
         tree[now].val=tree[x].val+tree[y].val;
     } else {
         tree[now].ls=merge(l,mid,tree[x].ls,tree[y].ls);
         tree[now].rs=merge(mid+1,r,tree[x].rs,tree[y].rs);
         tree[now].val=tree[tree[now].ls].val+tree[tree[now].rs].val;
     }
     killId(x),killId(y);
     return now;
 }

练习题:

可以考虑做一下Tree Rotation,大致题意是给一棵二叉树,可以交换每个点的左右子树,要求前序遍历叶子的逆序对最少。

由于左右儿子的交换不会影响更上层的值,所以在每次合并的时候直接统计即可。

原文地址:https://www.cnblogs.com/ilverene/p/11296781.html

时间: 2024-12-10 22:10:54

权值线段树&&线段树合并的相关文章

97: cf 983E 倍增+树套树

$des$一棵 $n$ 个点的树,树上有 $m$ 条双向的公交线路,每条公交线路都在两个节点之间沿最短路径往返.$q$ 次询问从一个点要到达另一个点,在只坐公交的情况下,至少需要坐几辆公交车:或者判断无法只坐公交到达.$n,m,q <= 2 \times 10^5$ $sol$对于每个点,先预处理出从这个点坐一次公交车能最远到达哪个祖先.对于一条公交线路 (u,v),将 lca 的信息挂在 u,v 上,dfs 一遍向上更新信息即可.通过倍增算出从某个点坐 $2^k$ 次最远能到达哪个祖先.这样对

数据结构第三部分:树与树的表示、二叉树及其遍历、二叉搜索树、平衡二叉树、堆、哈夫曼树、集合及其运算

参考:浙大数据结构(陈越.何钦铭)课件 1.树与树的表示 什么是树? 客观世界中许多事物存在层次关系 人类社会家谱 社会组织结构 图书信息管理 分层次组织在管理上具有更高的效率! 数据管理的基本操作之一:查找(根据某个给定关键字K,从集合R 中找出关键字与K 相同的记录).一个自然的问题就是,如何实现有效率的查找? 静态查找:集合中记录是固定的,没有插入和删除操作,只有查找 动态查找:集合中记录是动态变化的,除查找,还可能发生插入和删除 静态查找——方法一:顺序查找(时间复杂度O(n)) int

【数据结构】树与树的表示、二叉树存储结构及其遍历、二叉搜索树、平衡二叉树、堆、哈夫曼树与哈夫曼编码、集合及其运算

1.树与树的表示 什么是树? 客观世界中许多事物存在层次关系 人类社会家谱 社会组织结构 图书信息管理 分层次组织在管理上具有更高的效率! 数据管理的基本操作之一:查找(根据某个给定关键字K,从集合R 中找出关键字与K 相同的记录).一个自然的问题就是,如何实现有效率的查找? 静态查找:集合中记录是固定的,没有插入和删除操作,只有查找 动态查找:集合中记录是动态变化的,除查找,还可能发生插入和删除 静态查找--方法一:顺序查找(时间复杂度O(n)) int SequentialSearch(St

B20J_2733_[HNOI2012]永无乡_权值线段树合并

Description:n座岛,编号从1到n,每座岛都有自己的独一无二的重要度,按照重要度可以将这n座岛排名,名次用1到 n来表示.某些岛之间由巨大的桥连接,通过桥可以从一个岛到达另一个岛.现在有两种操作:B x y表示在岛 x与岛y之间修建一座新桥.Q x k表示询问当前与岛 x连通的所有岛中第k重要的是哪座岛,即所有与岛 x连通的岛中重要度排名第 k小的岛是哪座,请你输出那个岛的编号. 对于100%的数据n≤100000,m≤n,q≤300000. 分析:读懂题后发现是一道线段树合并的裸题.

Codeforces - 331B2 权值线段树 区间合并

题意:题目太玄了我无法用语言精简.. 题目要求的操作1是基于值的,所以用普通线段树基本无法维护(反正我不知道) 换做权值型后十分好做,因为连接处必然是更后面的,这时比较一下位置就好 PS.感觉周赛越来越硬核了 #include<bits/stdc++.h> #define rep(i,j,k) for(register int i=j;i<=k;i++) #define print(a) printf("%lld",(ll)(a)) #define println(a

【bzoj3065】带插入区间K小值 替罪羊树套权值线段树

题目描述 从前有n只跳蚤排成一行做早操,每只跳蚤都有自己的一个弹跳力a[i].跳蚤国王看着这些跳蚤国欣欣向荣的情景,感到非常高兴.这时跳蚤国王决定理性愉悦一下,查询区间k小值.他每次向它的随从伏特提出这样的问题: 从左往右第x个到第y个跳蚤中,a[i]第k小的值是多少.这可难不倒伏特,他在脑袋里使用函数式线段树前缀和的方法水掉了跳蚤国王的询问.这时伏特发现有些跳蚤跳久了弹跳力会有变化,有的会增大,有的会减少.这可难不倒伏特,他在脑袋里使用树状数组套线段树的方法水掉了跳蚤国王的询问.(orz 主席

【bzoj3702】二叉树 权值线段树

神奇的解法 对于每个节点,建出权值线段树 每次查询右子树的权值线段树和左子树的权值线段树,左子树中比右子树小的有多少?右子树比左子树小的有多少?(分别对应不交换的逆序对和交换的逆序对) 将左子树和右子树的权值线段树合并 递归进行这个操作 感觉复杂度很不靠谱,于是想证明一下复杂度 最开始权值线段树共O(nlogn)个节点,最后共O(n)个节点 每次合并两棵树的每个节点都要访问一遍,所以每个节点好像是要访问O(dep[i])次? 但是,合并两棵树后,有些重复的节点被合并到了一起 所以好像有些节点又没

Codeforces 666E Forensic Examination SAM+权值线段树

第一次做这种$SAM$带权值线段树合并的题 然而$zjq$神犇看完题一顿狂码就做出来了 $Orz$ 首先把所有串当成一个串建$SAM$ 我们对$SAM$上每个点 建一棵权值线段树 每个叶子节点表示一个匹配串能到达这个点的子串个数 这样我们对最后的$SAM$的权值线段树按$parent$树合并 询问的时候找到对应的$SAM$上的权值线段树直接查询就好了 具体的操作看代码吧~ #include<bits/stdc++.h> using namespace std; #define FO(x) {f

知识点 - 线段树 权值 树套树 二维 可持续

知识点 - 线段树 权值 树套树 二维 可持续 //区间更新求和 inline int ls(int p) { return p << 1; }//左儿子 inline int rs(int p) { return p << 1 | 1; }//右儿子 void push_up(int p) { t[p] = t[ls(p)] + t[rs(p)]; }// 向上不断维护区间操作 void build(ll p, ll l, ll r) { if (l == r) { t[p] =