简介
线段树是一种能够区间查询、区间修改的二叉树。
实现方式
信息学竞赛中,常使用数组下表模拟指针的方式实现。然而在实际情况中,我们并不知道数据量,因此采用动态分配内存,且无法保证堆空间中有正好足够大的连续未分配内存空间,因此节点一个个的申请内存空间。
实现
节点
struct Node
{
Node* lc;
Node* rc;
int left;
int right;
long long sum;
int add;
};
构建线段树
void built(Node* &ptr,int left,int right,int* array)
{
ptr = new Node();
ptr->left = left;
ptr->right = right;
ptr->lc = ptr->rc = NULL;
ptr->add = 0;
if(left == right)
{
ptr->sum = (long long)array[left];
return;
}
int mid = (left+right)/2;
built(ptr->lc,left,mid,array);
built(ptr->rc,mid+1,right,array);
ptr->sum = ptr->lc->sum+ptr->rc->sum;
}
标记下传
线段树若区间修改是将所有影响到的节点全部修改,那么将会使O(log n)的时间复杂度替换为O(n),因此我们使用标记表示影响,只修改刚好被覆盖的节点,只有在访问到受到影响却未修改的节点才下传标记,使子节点被修改。
下传标记
void spread(Node* ptr)
{
if(ptr->lc != NULL)
{
ptr->lc->sum += (long long)(ptr->add)*(ptr->lc->right-ptr->lc->left+1); //计算标记对左节点的影响
ptr->lc->add += ptr->add; //下传标记到左节点
}
if(ptr->rc != NULL)
{
ptr->rc->sum += (long long)(ptr->add)*(ptr->rc->right-ptr->rc->left+1); //计算标记对右节点的影响
ptr->rc->add += ptr->add; //下传标记到右节点
}
ptr->add = 0; //清零当前节点的标记
}
区间查询
long long query_sum(Node* ptr,int left,int right)
{
if(left <= ptr->left && right >= ptr->right) //正好节点范围在查询范围内
{
return ptr->sum;
}
spread(ptr);
long long ret = 0;
int mid = (ptr->left+ptr->right)/2;
if(left <= mid) ret += query_sum(ptr->lc,left,right); //左子节点范围与查询范围有重叠
if(right > mid) ret += query_sum(ptr->rc,left,right); //右子节点范围与查询范围有重叠
return ret;
}
区间修改
void change(Node* ptr,int left,int right,int d)
{
if(left <= ptr->left && right >= ptr->right) //正好节点范围在修改范围内
{
ptr->sum += (long long)d*(ptr->right-ptr->left+1);
ptr->add += d;
return;
}
spread(ptr);
int mid = (ptr->left+ptr->right)/2;
if(left <= mid) change(ptr->lc,left,right,d); //左子节点范围与修改范围有重叠
if(right > mid) change(ptr->rc,left,right,d); //右子节点范围与修改范围有重叠
ptr->sum = ptr->lc->sum+ptr->rc->sum;
}
清空申请的内存
清空不用的申请的内存,是个好习惯,避免内存溢出。
void remove(Node* ptr)
{
if(ptr->lc != NULL) remove(ptr->lc);
if(ptr->rc != NULL) remove(ptr->rc);
delete ptr;
}