目录
- 认识线段树和他的梦想
- 实现梦想之有个小身板
- 实现梦想之一点一点查
- 实现梦想之一点一点变
- 实现梦想之一坨一坨查
- 实现梦想之一坨一坨变
实现梦想之…不,没能力帮你了
1. 认识线段树和他的梦想
差分想必大家都认识,它是可以进行区间加与区间减,但是要查询某个区间的和。他每一次都要跑一遍,时间复杂度为 \(O(nq)\)。
还有ST表,它可以进行静态的以 \(O(1)\) 的时间复杂度去查询最大值与最小值。但是不能修改。
新朋友:线段树的梦想就是将以上两者的缺点优点结合。
当然,这只是入门,牛逼的是还想瘦身,有记忆,有……
具体写法
初始化
现在树它是一棵二叉树只不过它每一个节点不再代表一个点,而是代表一个区间。下面的图,以区间和为例。
注意到每一个点,它如果说不是叶子,它就一定有两个儿子。所以通常的话,我们保险起见,一般的开 \(4 \times n\)。
然后我们就可以以 \(O(nlogn)\) 的速度去遍历每一个点来完成初始化。顺带一提,通常我们存的时候不会用vector存树,而是会通过一个数组来存。
设当前点为 \(x\),则它的左儿子为 \(2x\) 右儿子为 \(2x+1\)。然后我们就可以得到初始化部分:
void pushup(int x)
{tree[x]=tree[x*2]+tree[x*2+1];//将左子树与右子树和加起来。
}
void build(int x,int l,int r)
{if(l==r)//如果说这个区间的左端点与右端点相同,那么说明我们走到了只有一个点的区间。{tree[x]=a[l];//赋值,注意,a是原数组,它的位置为l或者r。而线段树的下标为x。return;}int mid=(l+r)>>1;//我们以中心为分割点,这样可以保证我们的树高度在log n以内。build(x*2,l,mid);//走左子树。build(x*2+1,mid+1,r);//走右子树。pushup(x);//计算当前的加和。由于后面还会用到,所以在此我将它封装成了pushup。
}
单点查询
我们直接去寻找那个点然后返回,由于涉及到他的顶多只会有 \(\log n\) 个 \(g\) 它的所有祖先节点。所以在此它的速度为 \(\log n\)。
void query(int x,int l,int r,int p)
{if(l==r)//单个点,就是我们要找到的{return tree[x];//查}int mid=(l+r)>>1;//找if(mid>=p) return query(x*2,l,mid,p,num);//找else return update(x*2+1,mid+1,r,p,num);//找
}
单点修改
我们直接去寻找那个点然后将其改完之后更新整棵树。由于涉及到他的顶多只会有 \(\log n\) 个节点(即祖先节点)。所以在此它的速度为 \(\log n\)。
void pushup(int x)
{tree[x]=tree[x*2]+tree[x*2+1];//将左子树与右子树和加起来。
}
void update(int x,int l,int r,int p,int num)
{if(l==r)//单个点,就是我们要找到的{tree[x]+=num;//修return;}int mid=(l+r)>>1;//找if(mid>=p) update(x*2,l,mid,p,num);//找else update(x*2+1,mid+1,r,p,num);//找pushup(x);//回
}