线段树的一些细节区别 以区间和为例

本文深入探讨了线段树的不同流派及其应用,特别是在区间和计算中的实现细节。通过对比节点内存储边界与不存储边界的方法,分析了各自的优缺点。同时,详细介绍了更新和查询操作的两种方式,包括lazy加入sum和不加入sum的情况,提供了完整的示例代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线段树的几种流派 以区间和为例

节点内存储L、R

  • 需要很多存储空间,debug方便,写起来较下面一种方法慢

节点不内存储L、R

  • 节省大量存储空间,但是debug困难,并且push_down()不能写成函数(没有区间长度信息)

检测区间匹配

包含时就操作

直接暴力分割,在函数开头检测,不行就return。

完全匹配才操作

三种条件分割(左,右,中间),只有在中间才分割。

区间更新

更新时,lazy加入sum

  • 使用push_up()来更新
  • update时,需要push_down()

以下是示例代码(部分代码,poj3468,AC)

#define ti tree[i];
struct node
{
	int l;
	int r;
	long long sum;
	long long lazy;//此处的lazy加入sum
	int len()const { return r - l + 1; }
	void print()const { printf("%d %d %lld %lld\n", l, r, sum, lazy); }
};

const int maxn = 100000 + 10;
node tree[maxn * 4];
int arr[maxn];
#define ti tree[i]

void push_up(int i)
{
	tree[i].sum = tree[i * 2].sum + tree[i * 2 + 1].sum;
}

void push_down(int i)
{
	if (tree[i].l != tree[i].r)
	{
		tree[i * 2].lazy += tree[i].lazy;
		tree[i * 2 + 1].lazy += tree[i].lazy;
		tree[i * 2].sum += tree[i].lazy*tree[i * 2].len();
		tree[i * 2 + 1].sum += tree[i].lazy*tree[i * 2 + 1].len();
		tree[i].lazy = 0;
	}
}

void build(int i, int l, int r)
{
	ti.l = l;
	ti.r = r;
	if (l == r)
	{
		ti.sum = arr[r - 1];
		ti.lazy = 0;
		return;
	}
	int mid = (l + r) / 2;
	build(i * 2, l, mid);
	build(i * 2 + 1, mid + 1, r);
	push_up(i);
}

void update(int i, int ul, int ur, int val)
{
	if (ti.lazy)
		push_down(i);

	if (ti.l == ul && ti.r == ur)
	{
		ti.lazy += val;
		ti.sum += val * ti.len();
		return;
	}

	int mid = (ti.l + ti.r) / 2;
	if (ur <= mid)
	{
		update(i * 2, ul, ur, val);
		push_up(i);
	}
	else if (ul > mid)
	{
		update(i * 2 + 1, ul, ur, val);
		push_up(i);
	}
	else
	{
		update(i * 2, ul, mid, val);
		update(i * 2 + 1, mid + 1, ur, val);
		push_up(i);
	}
}

long long query(int i, int ql, int qr)
{
	if (ti.lazy)
		push_down(i);

	if (ti.l == ql && ti.r == qr)
	{
		return ti.sum;
	}

	int mid = (ti.l + ti.r) / 2;
	if (qr <= mid)
	{
		return query(i * 2, ql, qr);
	}
	else if (ql > mid)
	{
		return query(i * 2 + 1, ql, qr);
	}
	else
	{
		return query(i * 2, ql, mid) + query(i * 2 + 1, mid + 1, qr);
	}
}

void print(int i, int l, int r)
{
	ti.print();
	if (l != r)
	{
		int mid = (l + r) / 2;
		print(i * 2, l, mid);
		print(i * 2 + 1, mid + 1, r);
	}
}

更新时,lazy不加入sum

  • 更新时,途径节点的sum直接加上此区间包含的更新区间应加的值。(或者是在返回值里传递加上lazy后的sum值)
  • 如果更新没有顺序没有影响,可以update时不更新,留在query时再更新。还可以不更新,query时加上lazy对应区段加上的值。

顺序有影响的例子:区间涂色
顺序没有影响的例子:区间和

以下是示例代码(部分代码,poj3468,AC)

#define ti tree[i];
struct node
{
	int l;
	int r;
	long long sum;
	long long lazy;//此处的lazy不加入sum
	int len()const { return r - l + 1; }
	void print()const { printf("%d %d %lld %lld\n", l, r, sum, lazy); }
};

const int maxn = 100000 + 10;
node tree[maxn * 4];
int arr[maxn];
#define ti tree[i]

void push_up(int i)
{
	tree[i].sum = tree[i * 2].sum + tree[i * 2 + 1].sum;
}

void push_down(int i)
{
	if (tree[i].l != tree[i].r)
	{
		tree[i].sum += tree[i].lazy*tree[i].len();
		tree[i * 2].lazy += tree[i].lazy;
		tree[i * 2 + 1].lazy += tree[i].lazy;
		tree[i].lazy = 0;
	}
}

void build(int i, int l, int r)
{
	ti.l = l;
	ti.r = r;
	if (l == r)
	{
		ti.sum = arr[r - 1];
		ti.lazy = 0;
		return;
	}
	int mid = (l + r) / 2;
	build(i * 2, l, mid);
	build(i * 2 + 1, mid + 1, r);
	push_up(i);
}

void update(int i, int ul, int ur, int val)
{
	if (ti.lazy)
		push_down(i);

	if (ti.l == ul && ti.r == ur)
	{
		ti.lazy += val;
		return;
	}
	else
	{
		ti.sum += val * (ur - ul + 1);
	}

	int mid = (ti.l + ti.r) / 2;
	if (ur <= mid)
	{
		update(i * 2, ul, ur, val);
	}
	else if (ul > mid)
	{
		update(i * 2 + 1, ul, ur, val);
	}
	else
	{
		update(i * 2, ul, mid, val);
		update(i * 2 + 1, mid + 1, ur, val);
	}
}

long long query(int i, int ql, int qr)
{
	if (ti.lazy)
		push_down(i);

	if (ti.l == ql && ti.r == qr)
	{
		return ti.sum + ti.lazy*ti.len();
	}

	int mid = (ti.l + ti.r) / 2;
	if (qr <= mid)
	{
		return query(i * 2, ql, qr);
	}
	else if (ql > mid)
	{
		return query(i * 2 + 1, ql, qr);
	}
	else
	{
		return query(i * 2, ql, mid) + query(i * 2 + 1, mid + 1, qr);
	}
}

void print(int i, int l, int r)
{
	ti.print();
	if (l != r)
	{
		int mid = (l + r) / 2;
		print(i * 2, l, mid);
		print(i * 2 + 1, mid + 1, r);
	}
}
### 关于C++线段树实现中的错误分析 在线段树的实现过程中,可能会遇到多种类型的错误。以下是常见的几种原因及其可能引发的问题: #### 1. **未初始化变量** 如果在构建线段树之前没有对数组或其他存储结构进行清零处理,则可能导致残留数据干扰计算结果[^3]。 如,在某些情况下,`sum` 或 `lazy` 数组如果没有被正确初始化为零,后续的操作会受到先前数据的影响。 ```cpp // 初始化操作 memset(sum, 0, sizeof(sum)); memset(lazy, 0, sizeof(lazy)); ``` #### 2. **懒惰标记(Lazy Propagation)问题** 当使用带延迟更新的线段树时,忘记下传懒惰标记或者错误地实现了 `push_down` 函数,都会导致查询结果不准确[^2]。 ```cpp void push_down(int p, int l, int r) { if (lazy[p]) { int mid = (l + r) >> 1; lazy[left(p)] += lazy[p]; sum[left(p)] += (mid - l + 1) * lazy[p]; lazy[right(p)] += lazy[p]; sum[right(p)] += (r - mid) * lazy[p]; lazy[p] = 0; // 清除当前节点的懒惰标记 } } ``` #### 3. **边界条件处理不当** 对于区间的划分递归终止条件,如果不小心设置错误,可能会导致无限递归或错过目标区间[^4]。 如,在以下代码片段中,如果 `if(tree[i].l > X || tree[i].r < X)` 条件写错,就无法正常退出递归。 ```cpp void find(int i) { if (tree[i].l > X || tree[i].r < X) return ; ans += tree[i].cnt; find(i * 2); find(i * 2 + 1); } ``` #### 4. **内存分配不足** 由于线段树通常需要两倍甚至四倍的空间来存储节点信息,因此如果数组大小定义过小,就会发生越界访问。 建议按照实际需求合理规划空间大小,比如将数组容量设为 `N << 2`。 ```cpp const int MAX_N = 1e6 + 5; int segTree[MAX_N << 2]; // 开辟足够的空间 ``` #### 5. **逻辑错误** 有时算法本身的逻辑存在缺陷,如在解决特定题目时未能充分考虑特殊情况。以 CF527C Glass Carving 为,若未维护好最大矩形面积的变化过程,则难以得出正确答案[^5]。 --- ### 总结 综上所述,C++ 中线段树实现可能出现的错误主要包括但不限于以上几点。开发者应仔细检查每一处细节,并通过调试工具验证程序行为是否符合预期。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值