51nod-1462 数据结构 -- 解题报告
题意简述
¶一棵
1 u d
: 对 到根上所有点执行2 u d
: 对 到根上所有点执行
输出
数据范围:
数据保证
题目简析
¶虽然是树上的链的问题,但是通过树链剖分可以将其拆解成
更新操作
¶给线段树上的每个节点维护三个值
下面考虑两种操作对应的线段树更新流程:
操作一: 给
增加值若此时
不为 ,则需要执行一次pushdown操作:将 更新给左右子树,并且将 累加到 中,最后将 设置为 .pushdown.cpp1234567891011void pushdown(int o, int lft, int rht) {LL d = T[o];if (d == 0) return;T[o] = 0;S[o] += V[o] * d;if (lft < rht) {T[lc] += d;T[rc] += d;}}为什么要先执行一次pushdown呢?因为此时
代表的是在此次操作之前,节点 所代表的区间中所有的点 均需要加上 ,如果不先pushdown 则含义变成了 所代表的区间中所有的点 均需要加上 .之后,如果当前节点在目标区间内,则给
加上 。由此,不难写出操作一对应的线段树操作的代码:update1.cpp123456789101112void update1(int o, int lft, int rht) {pushdown(o, lft, rht);if (ul <= lft && rht <= ur) {V[o] += uv;return;}int mid = (lft + rht) >> 1;if (ul <= mid) update1(lc, lft, mid);if (ur > mid) update1(rc, mid + 1, rht);}操作二: 给
增加值( 和 的乘积)操作二比操作一略微复杂一些,除了要执行pushdown,还需要将沿途所有的
累加起来(不妨记为 ),直到到达在操作区间内的区间节点时,再将 累加给 ,同时将 累加给 .之所以要记录
是因为在pushdown操作中我们只往子节点传递了 ,而没有传递 [1],因此当前节点 的所有祖先节点的 值之和 对于此次操作二中 的值都是有贡献的,所以必须维护这部分结果。幸运的是,对于 所代表的区间中的所有点, 对它们是等效的,所以将其累加到 中即可完成这部分信息的维护。update2.cpp12345678910111213void update2(int o, int lft, int rht, LL acc) {if (ul <= lft && rht <= ur) {T[o] += uv;S[o] += acc;return;}acc += V[o] * uv;pushdown(o, lft, rht);int mid = (lft + rht) >> 1;if (ul <= mid) update2(lc, lft, mid, acc);if (ur > mid) update2(rc, mid + 1, rht, acc);}
查询操作
¶为方便表述,记原树中节点
在线段树中执行一次树上的递推(从根结点出发,沿途pushdown即可),则可以在
123456789101112void query(int o, int lft, int rht) { if (lft == rht) ans[lft] = S[o] + V[o] * T[o]; else { pushdown(o, lft, rht); S[lc] += S[o]; S[rc] += S[o]; int mid = lft + rht >> 1; query(lc, lft, mid); query(rc, mid + 1, rht); }}
复杂度分析
¶update1
和 update2
都是线段树的经典操作,复杂度均为 query
操作的复杂度和遍历树的复杂度相同,所以是 update
和 update2
是在树链剖分的基础上进行的,因此其复杂度需要再乘以
- 空间复杂度:
- 时间复杂度:
小结
¶数据范围比较大,会爆栈,需要手动扩栈 [2]。
1#pragma comment(linker, "/STACK:102400000,102400000")记区间长度为
,则线段树至多只需要 个节点。此外,预处理阶段(读入边时)使用到的数组可以和后续线段树中的数组进行复用,以此节约空间variables.cpp12345678910typedef long long LL;const int MAXN = 100000 + 10;const int MAXN2 = (1 << 17) + 10;int N, Q;int father[MAXN], dep[MAXN], son[MAXN], pos[MAXN], top[MAXN], dfsCnt;LL V[MAXN2 << 1], T[MAXN2 << 1], S[MAXN2 << 1], ans[MAXN];// 复用空间LL *from = V, *to = T, *nxt = S, *siz = ans;思路上是将操作分解,利用乘法分配律将操作二分解成两部分。总的来说还是利用了线段树维护区间的特点,每次pushdown(或maintain)都不能递归引发子节点的更新操作,则可以证明每次操作至多执行
次pushdown(或maintain),故而将单次操作的时间复杂度控制在 内
Related
¶