社交网站平台怎么做怎么建设微信网站
news/
2025/9/28 11:59:50/
文章来源:
社交网站平台怎么做,怎么建设微信网站,重庆h5建站模板,joomla 网站 html 空0x43 线段树
线段树#xff08;Segment Tree#xff09;是一种基于分治思想的二叉树结构#xff0c;用于在区间进行信息统计。与按照二进制位#xff08;2的次幂#xff09;进行区间划分的树状数组相比#xff0c;线段树是一种更加通用的结构#xff1a;
1.线段树的每…0x43 线段树
线段树Segment Tree是一种基于分治思想的二叉树结构用于在区间进行信息统计。与按照二进制位2的次幂进行区间划分的树状数组相比线段树是一种更加通用的结构
1.线段树的每个节点都代表一个区间。
2.线段树具有唯一的根节点代表的区间是整个统计范围如 [ 1 , N ] [1,N] [1,N]。
3.线段树的每个叶节点都代表一个长度为1的元区间 [ x , x ] [x,x] [x,x]。
4.对于每个内部节点 [ l , r ] [l,r] [l,r]它的左子节点是 [ l , m i d ] [l,mid] [l,mid]右子节点是 [ m i d 1 , r ] [mid1,r] [mid1,r]其中 m i d ( l r ) / 2 mid(lr)/2 mid(lr)/2向下取整。 上图展示了一棵线段树。可以发现除去数的最后一层整棵线段树一定是一棵完全二叉树树的深度为 O ( l o g N ) O(logN) O(logN)。因此我们可以按照与二叉堆类似的“父子2倍”节点编号方法
1.根节点编号为1。
2.编号为 x x x的节点的左子节点编号为 x ∗ 2 x*2 x∗2左子节点编号为 x ∗ 2 1 x*21 x∗21。
这样一来我们就能简单的使用一个 s t r u c t struct struct数组来保存线段树。当然树的最后一层节点在数组中保存的位置是不连续的直接空出数组中多余的位置即可。在理想情况下 N N N个节点的满二叉树有 N N / 2 N / 4 . . . 2 1 2 N − 1 NN/2N/4...212N-1 NN/2N/4...212N−1个节点。因为在上述储存方式下最后还有一层产生了空余所以保存线段树的数组长度要不小于 4 N 4N 4N才能保证不会越界。
线段树的建树
线段树的基本用途是对序列进行维护支持查询和修改指令。给定一个长度为 N N N的序列 A A A我们可以在区间 [ 1 , N ] [1,N] [1,N]上建立一棵线段树每个叶节点 [ i , i ] [i,i] [i,i]保存 A [ i ] A[i] A[i]的值。线段树的二叉树结构可以很方便地从下往上传递信息。以区间最大值问题为例记 d a t ( l , r ) dat(l,r) dat(l,r)等于 m a x l ≤ i ≤ r { A [ i ] } max_{l\leq i \leq r} \{ A[i]\} maxl≤i≤r{A[i]}显然 d a t ( l , r ) m a x ( d a t ( l , m i d ) , d a t ( m i d 1 , r ) ) dat(l,r)max(dat(l,mid),dat(mid1,r)) dat(l,r)max(dat(l,mid),dat(mid1,r))。 下面这段代码建立了一棵线段树并在每个节点上保存了对于区间的最大值。
struct SegmentTree{int l,r;int dat;
}t[SIZE*4]; //struct数组存储线段树void build(int p,int l,int r)
{t[p].ll,t[p].rr; //节点p代表区间[l,r]if(lr) //叶节点{t[p].data[l];return;}int mid(lr)/2;build(p*2,l,mid); //左子节点[l,mid]编号p*2build(p*21,mid1,r); //右子节点[mid1,r]编号p*21t[p].datmax(t[p*2].dat,t[p*21].dat); //从下往上传递信息
}线段树的单点修改
单点修改是一条形如“C x v”的指令表示把 A [ x ] A[x] A[x]的值修改为 v v v。
在线段树中根节点编号为1的节点是执行各种指令的入口。我们需要从根节点出发递归找到代表区间 [ x , x ] [x,x] [x,x]的叶节点然后从下往上更新 [ x , x ] [x,x] [x,x]以及它的所有祖先节点上保存的信息如下图所示。时间复杂度为 O ( l o g N ) O(logN) O(logN)。
void change(int p,int x,int v)
{if(t[p].lt[p].r){t[p].datv;return;}int mid(t[p].lt[p].r)/2;if(xmid)change(p*2,x,v);elsechange(p*21,x,v);t[p].datmax(t[p*2].dat,t[p*21].dat);
}线段树的区间查询
区间查询是一条形如“Q l r”的指令例如查询序列 A A A在区间 [ l , r ] [l,r] [l,r]上的最大值即 m a x l ≤ i ≤ r { A [ i ] } max_{l\leq i \leq r} \{A[i]\} maxl≤i≤r{A[i]}。我们只需要从根节点开始递归执行一下过程
1.若 [ l , r ] [l,r] [l,r]完全覆盖了当前节点代表的区间则立即回溯并且该节点的 d a t dat dat值为候选答案。
2.若左子节点与 [ l , r ] [l,r] [l,r]有重叠部分则递归访问左子节点。
3.若右子节点与 [ l , r ] [l,r] [l,r]有重叠部分则递归访问右子节点。
int ask(int p,int l,int r)
{if(lt[p].lrt[p].r)return t[p].dat; //完全包含int mid(t[p].lt[p].r)/2;int val-(130); //负无穷大if(lmid)valmax(val,ask(p*2,l,r)); //左子节点有重叠if(rmid)valmax(val,ask(p*21,l,r)); //右子节点有重叠return val;
}
coutask(1,l,r)endl; //调用入口该查询过程会把询问区间 [ l , r ] [l,r] [l,r]在线段树上分成 O ( l o g N ) O(logN) O(logN)个节点取它们的最大值作为答案。为什么是 O ( l o g N ) O(logN) O(logN)个呢在每个节点 [ p l , p r ] [p_l,p_r] [pl,pr]上设 m i d ( p l p r ) / 2 mid(p_lp_r)/2 mid(plpr)/2向下取整可能会出现一下几种情况
1. l ≤ p l ≤ p r ≤ r l\leq p_l \leq p_r \leq r l≤pl≤pr≤r即完全覆盖了当前节点直接返回。
2. p l ≤ l ≤ p r ≤ r p_l\leq l \leq p_r \leq r pl≤l≤pr≤r即只有 l l l处于节点之内。
1 l m i d lmid lmid只会递归右子树。
2 l ≤ m i d l\leq mid l≤mid虽然递归两棵子树但是右子节点会在递归后直接返回。
3. l ≤ p l ≤ r ≤ p r l \leq p_l \leq r \leq p_r l≤pl≤r≤pr即只有 r r r位于节点之内与情况2类似。
4. p l ≤ l ≤ r ≤ p r p_l \leq l \leq r \leq p_r pl≤l≤r≤pr即 l l l与 r r r都位于节点之内。
1 l , r l,r l,r都位于 m i d mid mid一侧只会递归一棵子树。
2 l , r l,r l,r分别位于 m i d mid mid两侧递归左右两棵子树。
也就是说只有情况42会真正产生对左右两棵子树的递归。这种情况至多产生一次之后在子节点上就会变成情况2或3。因此上述查询过程的时间复杂度为 O ( 2 l o g N ) O ( l o g N ) O(2logN)O(logN) O(2logN)O(logN)。
至此线段树已经能像0x06节的ST算法一样处理区间最值问题并且还支持动态修改某个数的值。同时线段树也已经能支持上一节树状数组的单点增加与查询前缀和指令。
1.延迟标记懒标记
在线段树的“区间查询“指令中每当遇到被询问区间 [ l , r ] [l,r] [l,r]完全覆盖的节点时可以立即把该节点上储存的信息作为候选答案返回。我们已经证明被询问区间 [ l , r ] [l,r] [l,r]在线段树上会被分成 O ( l o g N ) O(logN) O(logN)个小区间节点从而在 O ( l o g N ) O(logN) O(logN)的时间内求出答案。不过在”区间修改“指令中如果每个节点被修改区间 [ l , r ] [l,r] [l,r]完全覆盖那么以该节点为根的整棵子树的所有节点储存的信息都会发生变化若逐一更新将使得一次区间修改指令的时间复杂度增加到 O ( N ) O(N) O(N)这是我们不能接受的。
如果我们在一次修改指令中发现节点 p p p代表的区间 [ p l , p r ] [p_l,p_r] [pl,pr]被修改区间 [ l , r ] [l,r] [l,r]完全覆盖并逐一更新了子树 p p p的所有节点但是之后的查询指令中却完全没有用到 [ l , r ] [l,r] [l,r]的子区间作为候选答案那么更新 p p p的整棵子树就是徒劳的。
换言之我们在执行修改指令时同样可以在 l ≤ p l ≤ p r ≤ r l\leq p_l\leq p_r\leq r l≤pl≤pr≤r的情况下立即返回只不过在回溯之前箱节点 p p p增加一个标记标识“该节点曾经被修改但其子节点尚未被更新”。
如果在后续的指令中需要从节点 p p p向下递归我们检查 p p p是否具有标记。若有标记就根据标记信息更新 p p p的两个子节点同时为 p p p的两个子节点增加标记然后清除 p p p的标记。
也就是说除了在修改指令中直接划分成的 O ( l o g N ) O(logN) O(logN)个节点之外对任意节点的修改都延迟到“在后续操作中递归进入它的父节点时”再执行。这样一来每条查询或修改指令的时间复杂度都降低到了 O ( l o g N ) O(logN) O(logN)。这些标记被称为“延迟标记”。延迟标记提供了线段树从上往下传递信息的方式。这种“延迟”也是设计算法与解决问题的一个重要思路。
上一节0x42节树状数组中我们解决了数字序列区间增长和区间查询和的问题我们也可以利用线段树的延迟标记来解决这里多了一个 s p r e a d spread spread函数实现了延迟标记的向下传递。
#include bits/stdc.h
using namespace std;typedef long long ll;
int N, Q;
int a[100005];
struct SegmentTree
{int l, r;ll sum, add;
} t[400005];void build(int p, int l, int r)
{t[p].l l, t[p].r r;if (l r){t[p].sum a[l];return;}int mid (l r) / 2;build(p * 2, l, mid);build(p * 2 1, mid 1, r);t[p].sum t[p * 2].sum t[2 * p 1].sum;
}void spread(int p)
{if (t[p].add){t[p * 2].sum (ll)(t[p * 2].r - t[p * 2].l 1) * t[p].add;t[p * 2].add t[p].add;t[p * 2 1].sum (ll)(t[p * 2 1].r - t[p * 2 1].l 1) * t[p].add;t[p * 2 1].add t[p].add;t[p].add 0;}
}void change(int p, int l, int r, int d)
{if (t[p].l l t[p].r r){t[p].sum (ll)(t[p].r - t[p].l 1) * d;t[p].add d;return;}spread(p);int mid (t[p].l t[p].r) / 2;if (l mid)change(p * 2, l, r, d);if (r mid)change(p * 2 1, l, r, d);t[p].sum t[p * 2].sum t[p * 2 1].sum;
}ll ask(int p, int l, int r)
{if (t[p].l l t[p].r r)return t[p].sum;spread(p);int mid (t[p].l t[p].r) / 2;ll ans 0;if (l mid)ans ask(p * 2, l, r);if (r mid)ans ask(p * 2 1, l, r);return ans;
}int main()
{scanf(%d%d, N, Q);for (int i 1; i N; i)scanf(%d, a[i]);build(1, 1, N);char op;int a, b, c;while (Q--){cin op;if (op Q){scanf(%d%d, a, b);printf(%lld\n, ask(1, a, b));}else{scanf(%d%d%d, a, b, c);change(1, a, b, c);}}return 0;
}需要指出的是延迟标记的含义是“该节点曾经被修改但其子节点尚未被更新”即延迟标记标识的是子节点等待更新的情况。因此一个子节点被打上“延迟标记”的同时它保存的信息应该被修改完毕。在编写代码时一定要注意“更新信息”与“打标记”之间的关系避免出现错误。
2.扫描线
给定平面直角坐标系中的 N N N个矩形求它们的面积并即这些矩形的并集在坐标系中覆盖的总面积如下图所示。 如果我们用一根竖直直线从左往右扫过整个坐标系那么直线上被并集图形覆盖的长度只会在每个矩形的左右边界处发生变化。
换言之整个并集图形可以被分成 2 ∗ N 2*N 2∗N段每一段在直线上覆盖的长度记为 L L L是固定的因此该段的面积就是 L ∗ L* L∗该段的宽度各段面积之和即为所求。这条直线就称为扫描线这种解题思路被称为扫描线法。
具体来说我们可以取出 N N N个矩形的左右边界。若一个矩形的两个对角顶点坐标为 ( x 1 , y 1 ) (x_1,y_1) (x1,y1)和 ( x 2 , y 2 ) (x_2,y_2) (x2,y2)不妨设 x 1 x 2 , y 1 y 2 x_1x_2,y_1y_2 x1x2,y1y2则左边界记为四元组 ( x 1 , y 1 , y 2 , 1 ) (x_1,y_1,y_2,1) (x1,y1,y2,1)右边界记为四元组 ( x 2 , y 1 , y 2 , − 1 ) (x_2,y_1,y_2,-1) (x2,y1,y2,−1)。把这 2 N 2N 2N个四元组按照 x x x递增排序如下图所示。 注意到本题中的 y y y坐标范围较大且不一定是整数我们先把输入的数据中出现的所有 y y y坐标放入一个数组排序、去重完成离散化。设 v a l ( y ) val(y) val(y)表示 y y y被离散化之后映射到的整数值 r a w ( i ) raw(i) raw(i)表示整数值 i i i对应的原始 y y y坐标值。
在离散化后若有 M M M个不同的 y y y坐标值分别对应 r a w ( 1 ) , r a w ( 2 ) , . . . , r a w ( M ) raw(1),raw(2),...,raw(M) raw(1),raw(2),...,raw(M)则扫描线至多被分成 M − 1 M-1 M−1段其中第 i i i段为区间 [ r a w ( i ) , r a w ( i 1 ) ] [raw(i),raw(i1)] [raw(i),raw(i1)]。建立数组 c c c用 c [ i ] c[i] c[i]记录扫描线上第 i i i段被覆盖的次数。起初 c c c数组中的元素全为0。
逐一扫描排序后的 2 N 2N 2N个四元组设当前四元组为 ( x , y 1 , y 2 , k ) (x,y_1,y_2,k) (x,y1,y2,k)。我们把数组 c c c中 c [ v a l ( y 1 ) ] , c [ v a l ( y 1 ) 1 ] , . . . , c [ v a l ( y 2 ) − 1 ] c[val(y_1)],c[val(y_1)1],...,c[val(y_2)-1] c[val(y1)],c[val(y1)1],...,c[val(y2)−1]把这些值都加上 k k k相当于覆盖了 [ y 1 , y 2 ] [y_1,y_2] [y1,y2]这个区间。此时如果下一个四元组的横坐标为 x 2 x_2 x2则扫描线从 x x x扫到 x 2 x_2 x2的过程中被覆盖的长度就固定为 ∑ c [ i ] 0 ( r a w ( i 1 ) − r a w ( i ) ) \sum_{c[i]0} (raw(i1)-raw(i)) ∑c[i]0(raw(i1)−raw(i))即数组 c c c中至少被覆盖一次的“段”的总长度。于是我们就让最终的答案 a n s ans ans累加上 ( x 2 − x ) ∗ ∑ c [ i ] 0 ( r a w ( i 1 ) − r a w ( i ) ) (x_2-x)*\sum_{c[i]0} (raw(i1)-raw(i)) (x2−x)∗∑c[i]0(raw(i1)−raw(i))。
对于每个四元组采用朴素算法在 c c c数组上执行修改与统计即可在 O ( N 2 ) O(N^2) O(N2)的时间内求出并集图形的面积。
我们可以用线段树维护 c c c数组把算法优化到 O ( N l o g N ) O(NlogN) O(NlogN)。
本题中我们只关心整个扫描线线段树根节点上被矩形覆盖的长度。而且因为四元组 ( x , y 1 , y 2 , 1 ) (x,y_1,y_2,1) (x,y1,y2,1)和 ( x , y 1 , y 2 , − 1 ) (x,y_1,y_2,-1) (x,y1,y2,−1)成对出现所以线段树区间修改也是成对出现的。在这种特殊情形下我们没有必要下传延迟标记可以采用更为简单的做法。
除左右端点 l , r l,r l,r之外在线段树的每个节点上维护两个值该节点代表的区间被矩形覆盖的长度 l e n len len该节点自身被覆盖的次数 c n t cnt cnt。最初两者均为0。
对于一个四元组 ( x , y 1 , y 2 , k ) (x,y_1,y_2,k) (x,y1,y2,k)我们在 [ v a l ( y 1 ) , v a l ( y 2 ) − 1 ] [val(y_1),val(y_2)-1] [val(y1),val(y2)−1]上执行区间修改。该区间被线段树划分成 O ( l o g N ) O(logN) O(logN)个节点我们把这些节点的 c n t cnt cnt都加 k k k。
对于线段树中任意一个节点 [ l , r ] [l,r] [l,r]若 c n t 0 cnt0 cnt0则 l e n len len等于 r a w ( r 1 ) − r a w ( l ) raw(r1)-raw(l) raw(r1)−raw(l)。否则该点 l e n len len等于两个子节点的 l e n len len之和。在一个节点的 c n t cnt cnt值被修改以及线段树从下往上传递信息时我们都按照该方法更新 l e n len len值。根节点的 l e n len len值就是整个扫描线上被覆盖的长度。
因为在扫入四元组 ( x , y 1 , y 2 , 1 ) (x,y_1,y_2,1) (x,y1,y2,1)后进行区间修改 c h a n g e change change操作被分成的 l o g N logN logN个区间 c n t cnt cnt值加1如果没有扫入四元组 ( x , y 1 , y 2 , − 1 ) (x,y_1,y_2,-1) (x,y1,y2,−1)则这被分成的 l o g N logN logN个区间 c n t cnt cnt值不可能为0就算扫入其他出边之前也扫入了对应的入边所以这被分成的 l o g N logN logN个区间 c n t cnt cnt值不可能为0只有当扫入对应的四元组 ( x , y 1 , y 2 , − 1 ) (x,y_1,y_2,-1) (x,y1,y2,−1)这被分成的 l o g N logN logN个区间 c n t cnt cnt值才可能为0。所以按照上述方法修改而不使用延迟修改是行得通的。
#include iostream
#include stdio.h
#include algorithm
using namespace std;#define l(p) t[p].l
#define r(p) t[p].r
#define ls (p1)
#define rs (p1|1)
const int SIZE1e5;
int N,M,now;
double a[2*SIZE],b[2*SIZE];
struct Line{double x,y1,y2;int k;bool operator(const Line b)const{return xb.x;}
}line[2*SIZE];
struct SegmentTree{int l,r;int cnt;double len;
}t[8*SIZE];void build(int p,int l,int r)
{l(p)l,r(p)r;if(lr){t[p].cnt0;t[p].len0;return;}int mid(lr)/2;build(ls,l,mid);build(rs,mid1,r);t[p].cnt0;t[p].len0;
}void pushup(int p)
{if(t[p].cnt0)t[p].lenb[r(p)1]-b[l(p)];else{if(l(p)!r(p))t[p].lent[ls].lent[rs].len;elset[p].len0;}
}void change(int p,int l,int r,int k)
{if(l(p)lr(p)r){t[p].cntk;pushup(p);return;}int mid(l(p)r(p))/2;if(lmid)change(ls,l,r,k);if(rmid)change(rs,l,r,k);pushup(p);
}int main()
{while(scanf(%d,N)N){now;M0;double x1,y1,x2,y2;for(int i1;iN;i){scanf(%lf%lf%lf%lf,x1,y1,x2,y2);line[i*2-1].xx1,line[i*2-1].y1y1,line[i*2-1].y2y2,line[i*2-1].k1;line[i*2].xx2,line[i*2].y1y1,line[i*2].y2y2,line[i*2].k-1;a[i*2-1]y1;a[i*2]y2;}N1;sort(line1,lineN1);sort(a1,aN1);for(int i1;iN;i){if(i1||a[i]!a[i-1])b[M]a[i];}build(1,1,M-1);double ans0;for(int i1;iN;i){int id1lower_bound(b1,bM1,line[i].y1)-b;int id2lower_bound(b1,bM1,line[i].y2)-b;change(1,id1,id2-1,line[i].k);ans(line[i1].x-line[i].x)*t[1].len;}printf(Test case #%d\nTotal explored area: %.2f\n\n,now,ans);}return 0;
}注意若数据有 N N N组那记录扫描线有 2 N 2N 2N组则线段树大小至少开到 8 N 8N 8N。注意在 p u s h u p pushup pushup操作中我们要注意叶节点的判断例如在 [ 1 , 10 ] [1,10] [1,10]建线段树叶节点区间 [ 7 , 7 ] [7,7] [7,7]编号为25如果不判断叶节点那他的 p u s h u p pushup pushup可能会用到50和51号节点超过了40超过了 4 ∗ s i z e 4*size 4∗size如果不想判断叶节点则线段树开到80的大小即开到 16 N 16N 16N 8 ∗ s i z e 8*size 8∗size。但要注意如果有多组测试数据会重复使用覆盖同一个线段树则想要使用 16 N 16N 16N 8 ∗ s i z e 8*size 8∗size需要在使用下一组数据前将整个线段树重置。
3.动态开点和线段树合并
在一些计数问题中线段树用于维护值域一段权值范围这样的线段树也称为权值线段树。为了降低空间复杂度我们可以不建出整棵线段树的结构而是在最初只建立一个根节点代表整个区间当需要访问线段树的某棵子树某个区间时再建立代表这个子区间的节点。采用这种方法维护的线段树称为动态开点的线段树。动态开点的线段树抛弃了完全二叉树父子节点的2倍编号规则改为使用变量记录左右子节点的编号相当于指针。同时它也不再保存每个节点代表的区间而是在每次递归访问的过程中作为参数传递。下面是一个动态开点的线段树的节点结构。
struct SegmentTree{int lc,rc; //左右子节点的编号int dat; //区间最大值
}tr[SIZE*2];int build() //新建一个节点
{tot;tr[tot].lctr[tot].rctr[tot].dat0;return tot;
}//在main函数中
tot0;
rootbuild(); //根节点下面的代码对线段树单点修改的过程稍加变动实现了在动态开点的线段树中把 v a l val val位置上值加 d e l t a delta delta同时维护区间最大值的操作。
void insert(int p,int l,int r,int val,int delta)
{if(lr){tr[p].datdelta;return;}int mid(lr)1;if(valmid){if(!tr[p].lc)tr[p].lcbuild(); //动态开点insert(tr[p].lc,l,mid,val,delta);}else{if(!tr[p].rc)tr[p].rcbuild(); //动态开点insert(tr[p].rc,mid1,r,val,delta);}tr[p].datmax(tr[tr[p].lc].dat,tr[tr[p].rc].dat);
}
insert(root,1,n,val,delta); //调用常规线段树的其他操作也可以通过类似的变动在动态开点的线段树上实现这里就不在赘述。一棵维护值域 [ 1 , n ] [1,n] [1,n]的动态开点的线段树在经历 m m m次单点操作后节点数量的规模为 O ( m l o g n ) O(mlogn) O(mlogn)最终至多有 2 n − 1 2n-1 2n−1个节点。
如果有若干颗线段树它们都维护相同的值域 [ 1 , n ] [1,n] [1,n]那么它们对各个子区间的规划显然是一致的。假如有 m m m次单点修改操作每次操作在某一棵线段树上执行。所有操作完成后我们希望把这些线段树对应位置上的值相加同时维护区间的最大值。
该问题可以通过线段树合并算法实现。我们依次合并这些线段树。合并两颗线段树时用两个指针 p , q p,q p,q从两个根节点出发以递归的方式同步遍历两颗线段树。换句话说 p , q p,q p,q指向的节点总是代表相同的子区间。
1.若 p , q p,q p,q之一为空则以非空的那个作为合并后的节点。
2.若 p , q p,q p,q均不为空则递归合并两颗左子树和两颗右子树然后删除节点 q q q以 p p p为合并后的节点自底向上更新最值信息。若已到达叶子节点则直接把两个最值相加即可。
int merge(int p,int q,int l,int r)
{if(!p) return q;if(!q) return p;if(lr){tr[p].dattr[q].dat;return p;}int mid(lr)1;tr[p].lcmerge(tr[p].lc,tr[q].lc,l,mid);tr[p].rcmerge(tr[p].rc,tr[q].rc,mid1,r);tr[p].datmax(tr[tr[p].lc].dat,tr[tr[p].rc].dat);return p; //以p作为合并后的节点相当于删除q
}仔细观察可以发现若线段树合并过程中发生递归则必定会导致 p , q p,q p,q之一被删除。因此在完成所有线段树的合并后 m e r g e merge merge函数被执行的次数不会超过所有线段树的节点总数加一。故这个合并过程的时间复杂度为 O ( m l o g n ) O(mlogn) O(mlogn)与完成所有单点修改操作的时间复杂度相等是一个很高效的过程。
我们会在0x63节结合具体例题探讨线段树合并的应用并完成代码实现。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/920605.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!