1. この問題の線分木配列データと構造
data[]={1,2,-3,5,6,-2,7,1,12,30,-10}、11 要素。
第二に、それぞれの機能と構造
(1) 線分ツリー構造
線分ツリーの構造を作成します。lとrは左右の境界、maxVとminVは最大値と最小値、sumはsum、tagはlazyTagです。
typedef struct SegNode{
int l,r,maxV,minV,sum,tag;
struct SegNode *pL, *pR;
}SegNode, *SegTree;
#define OK 1
#define ERROR 0
(2) 線分ツリーの作成
線分ツリーを作成する際、ポインタ変数の値を変更したい場合は、ポインタ変数SegTreeのアドレスを渡す必要があります。ダブルポインタはポインタのポインタ(アドレスのアドレス)を表すため、ここでは、 *SegTree を使用して SegTree のアドレスを指すようにダブル ポインターを使用します。
再帰的に作成され、境界 = 境界の場合、出口は左に再帰的に作成されます。
void BuildSegTree(SegTree* root, int l, int r, int data[]){
//当l == r时,左右边界为相同位置,递归结束
if(l == r){
(*root) = new SegNode;
(*root)->sum = data[l]; //只有一个数,所以和就是这个数
(*root)->maxV = data[l]; //同上
(*root)->minV = data[l]; //同上
(*root)->tag = 0; //新结点没有“欠债”
(*root)->l = l;
(*root)->r = r;
(*root)->pL = NULL; //左右子树为空
(*root)->pR = NULL;
return;
}
//左右边界不相同时,创建该结点后,分成左右两个子树继续递归
else{
(*root) = new SegNode;
(*root)->l = l;
(*root)->r = r;
(*root)->tag = 0;
int mid = l + (r-l)/2; //求中间位置
//递归调用该函数创建左右子树
BuildSegTree(&((*root)->pL), l, mid, data);
BuildSegTree(&((*root)->pR),mid+1, r, data);
(*root)->sum = (*root)->pL->sum + (*root)->pR->sum; //通过两个子树的和求该树的和
(*root)->maxV = max((*root)->pL->maxV, (*root)->pR->maxV); //通过两个子树的最大值比较求该树的最大值
(*root)->minV = min((*root)->pL->minV,(*root)->pR->minV); //通过两个子树的最小值比较求该树的最小值
}
}
(3) 遅延マークのクリア
遅延マーキングの「負債」を解消する機能です。遅延マーキングは効率を大幅に向上させる操作です。使用方法は、一定範囲のデータの値が変更されるたびに、最初に現在のノードのみが変更され、現在のノードの遅延に変更対象の値が存在しており、タグ内では、今後他のメソッドを下位ノードに再帰する必要がある場合、サブツリーに遅延タグが渡され、サブツリーの値が変更されます。
//清算懒标记“债务”的函数
void LateUpdate(SegTree root){
//没债务返回就行
if(root->tag == 0)
return;
//左子树不为空,把债务传递给左子树,并且更改左子树的值
if(root->pL != NULL){
root->pL->tag += root->tag;
root->pL->sum += (root->pL->r - root->pL->l+1)*root->tag;
root->pL->maxV += (root->tag);
root->pL->minV += (root->tag);
}
//右子树同上
if(root->pR != NULL){
root->pR->tag = root->tag;
root->pR->sum = (root->pL->r - root->pL->l+1)*root->tag;
root->pR->maxV += (root->tag);
root->pR->minV += (root->tag);
}
root->tag = 0; //债务结算完清空
}
(3) クエリ動作
最大値、最小値、および をクエリします。再帰的な操作では、クエリ範囲が現在のルート範囲とまったく同じになることが終了します。再帰的プロセスには 3 つの状況があります。 1. 中央の左側。2. ミッドの右側。3. 両面。
//查询操作
int query(SegTree root, int l, int r, int *tsum, int *tmax, int *tmin){
//如果要查询的左边界范围大于右边界或者相反,返回
if(r < root->l || l > root->r)
return ERROR;
//如果刚好查询的范围与当前root的范围相等
if(l == root->l && r == root->r){
*tsum = root->sum;
*tmax = root->maxV;
*tmin = root->minV;
return OK;
}
LateUpdate(root); //运行到这说明要向子树递归,所以先将债务传递给子树
int mid = root->l + (root->r - root->l)/2;
//整个在mid左边的情况
if(r<=mid){
query(root->pL, l, r, tsum, tmax, tmin);
return OK;
}
//整个在mid右边的情况
else if(l >= mid+1){
query(root->pR, l, r, tsum, tmax,tmin);
return OK;
}
//被mid从中间分开的情况
else{
int lsum, rsum, lmax,rmax, lmin, rmin;
query(root->pL, l, mid, &lsum, &lmax, &lmin);
query(root->pR, mid+1, r, &rsum, &rmax, &rmin);
*tsum = lsum+rsum; //计算左右子树和
*tmax = max(lmax,rmax); //从左右子树中找最大值
*tmin = min(lmin,rmin); //从左右子树中找最小值
return OK;
}
}
(4) 範囲内の各数値を数値で増加します。
//在一个范围内将每个数增加value
void Add(int tl, int tr, SegTree root, int Value){
//判断是否越界
if(tl > root->r || tr < root->l){
return;
}
//如果包含在内了,就把所有的值修改,并给tag写上债务
if(tl <= root->l && tr >= root->r){
root->sum += (root->r - root->l+1)*Value;
root->maxV += Value;
root->minV += Value;
root->tag += Value;
return;
}
//如果没包含,那就先将债务传递到子树,然后递归
LateUpdate(root);
int mid = root->l+(root->r-root->l)/2;
//都在左侧的情况
if(tr <= mid)
Add(tl, tr, root->pL, Value);
//都在右侧的情况
else if(tl >= mid+1)
Add(tl, tr, root->pR, Value);
//两边都有
else{
Add(tl, mid, root->pL, Value);
Add(mid+1, tr, root->pR, Value);
}
//计算各值
root->sum = root->pL->sum + root->pR->sum;
root->maxV = max(root->pL->maxV, root->pR->maxV);
root->minV = min(root->pL->minV, root->pR->minV);
}
(5) セグメントツリーを破壊する
void DestroySegTree(SegTree *root){
if(*root == NULL){
return;
}
if((*root)->pL == NULL && (*root)->pR == NULL){
delete *root;
*root = NULL;
}
else{
if((*root)->pL != NULL){
DestroySegTree(&((*root)->pL));
}
if((*root)->pR != NULL){
DestroySegTree(&((*root)->pR));
}
delete(*root);
}
}
3. すべてのコード
#include <iostream>
#include <map>
#include <math.h>
#include <cctype>
using namespace std;
//创建线段树的结构, l、r为左边界和右边界,maxV和minV为最大值和最小值,sum为和,tag为lazyTag
typedef struct SegNode{
int l,r,maxV,minV,sum,tag;
struct SegNode *pL, *pR;
}SegNode, *SegTree;
#define OK 1
#define ERROR 0
//创建一个线段树,segTreesh
void BuildSegTree(SegTree* root, int l, int r, int data[]){
//当l == r时,左右边界为相同位置,递归结束
if(l == r){
(*root) = new SegNode;
(*root)->sum = data[l]; //只有一个数,所以和就是这个数
(*root)->maxV = data[l]; //同上
(*root)->minV = data[l]; //同上
(*root)->tag = 0; //新结点没有“欠债”
(*root)->l = l;
(*root)->r = r;
(*root)->pL = NULL; //左右子树为空
(*root)->pR = NULL;
return;
}
//左右边界不相同时,创建该结点后,分成左右两个子树继续递归
else{
(*root) = new SegNode;
(*root)->l = l;
(*root)->r = r;
(*root)->tag = 0;
int mid = l + (r-l)/2; //求中间位置
//递归调用该函数创建左右子树
BuildSegTree(&((*root)->pL), l, mid, data);
BuildSegTree(&((*root)->pR),mid+1, r, data);
(*root)->sum = (*root)->pL->sum + (*root)->pR->sum; //通过两个子树的和求该树的和
(*root)->maxV = max((*root)->pL->maxV, (*root)->pR->maxV); //通过两个子树的最大值比较求该树的最大值
(*root)->minV = min((*root)->pL->minV,(*root)->pR->minV); //通过两个子树的最小值比较求该树的最小值
}
}
//先序遍历输出测试
void preOrder(SegTree root){
if(root == NULL){
return;
}
cout << "(" << root->l << "->" << root->r << ") sum: " << root->sum << " maxV: " << root->maxV << " minV: " << root->minV << ' ' <<endl;
preOrder(root->pL);
preOrder(root->pR);
}
//清算懒标记“债务”的函数
void LateUpdate(SegTree root){
//没债务返回就行
if(root->tag == 0)
return;
//左子树不为空,把债务传递给左子树,并且更改左子树的值
if(root->pL != NULL){
root->pL->tag += root->tag;
root->pL->sum += (root->pL->r - root->pL->l+1)*root->tag;
root->pL->maxV += (root->tag);
root->pL->minV += (root->tag);
}
//右子树同上
if(root->pR != NULL){
root->pR->tag = root->tag;
root->pR->sum = (root->pL->r - root->pL->l+1)*root->tag;
root->pR->maxV += (root->tag);
root->pR->minV += (root->tag);
}
root->tag = 0; //债务结算完清空
}
//查询操作
int query(SegTree root, int l, int r, int *tsum, int *tmax, int *tmin){
//如果要查询的左边界范围大于右边界或者相反,返回
if(r < root->l || l > root->r)
return ERROR;
//如果刚好查询的范围与当前root的范围相等
if(l == root->l && r == root->r){
*tsum = root->sum;
*tmax = root->maxV;
*tmin = root->minV;
return OK;
}
LateUpdate(root); //运行到这说明要向子树递归,所以先将债务传递给子树
int mid = root->l + (root->r - root->l)/2;
//整个在mid左边的情况
if(r<=mid){
query(root->pL, l, r, tsum, tmax, tmin);
return OK;
}
//整个在mid右边的情况
else if(l >= mid+1){
query(root->pR, l, r, tsum, tmax,tmin);
return OK;
}
//被mid从中间分开的情况
else{
int lsum, rsum, lmax,rmax, lmin, rmin;
query(root->pL, l, mid, &lsum, &lmax, &lmin);
query(root->pR, mid+1, r, &rsum, &rmax, &rmin);
*tsum = lsum+rsum; //计算左右子树和
*tmax = max(lmax,rmax); //从左右子树中找最大值
*tmin = min(lmin,rmin); //从左右子树中找最小值
return OK;
}
}
//在一个范围内将每个数增加value
void Add(int tl, int tr, SegTree root, int Value){
//判断是否越界
if(tl > root->r || tr < root->l){
return;
}
//如果包含在内了,就把所有的值修改,并给tag写上债务
if(tl <= root->l && tr >= root->r){
root->sum += (root->r - root->l+1)*Value;
root->maxV += Value;
root->minV += Value;
root->tag += Value;
return;
}
//如果没包含,那就先将债务传递到子树,然后递归
LateUpdate(root);
int mid = root->l+(root->r-root->l)/2;
//都在左侧的情况
if(tr <= mid)
Add(tl, tr, root->pL, Value);
//都在右侧的情况
else if(tl >= mid+1)
Add(tl, tr, root->pR, Value);
//两边都有
else{
Add(tl, mid, root->pL, Value);
Add(mid+1, tr, root->pR, Value);
}
//计算各值
root->sum = root->pL->sum + root->pR->sum;
root->maxV = max(root->pL->maxV, root->pR->maxV);
root->minV = min(root->pL->minV, root->pR->minV);
}
//销毁线段树
void DestroySegTree(SegTree *root){
if(*root == NULL){
return;
}
if((*root)->pL == NULL && (*root)->pR == NULL){
delete *root;
*root = NULL;
}
else{
if((*root)->pL != NULL){
DestroySegTree(&((*root)->pL));
}
if((*root)->pR != NULL){
DestroySegTree(&((*root)->pR));
}
delete(*root);
}
}
int main(){
SegTree root = NULL; //创建线段树
int data[]={1,2,-3,5,6,-2,7,1,12,30,-10}; //输入一些数据
int ssum, smax, smin;
BuildSegTree(&root, 0, 10, data); //构建线段树
preOrder(root); //先序遍历测试
query(root, 0, 7, &ssum, &smax, &smin); //求0~7的和
cout << endl << "0~7的和为:" << ssum << ' ' << smax << ' ' << smin << endl << endl;
Add(0, 7, root, 3); //给0~7增加3
query(root, 0, 7, &ssum, &smax, &smin); //求0~7的和
preOrder(root);
cout << endl << "加3后,0~7的和为:" << ssum << ' ' << smax << ' ' << smin << endl;
return 0;
}
4. 走行結果