线段树哲学-个人总结

i207M

2018-06-02 17:29:09

Personal

# 线段树哲学 **写完线段树读一遍有没有漏掉pushup和pushdown** ## 建树方法 为了加快时间,我采用堆的方法,即点$x$的左儿子是$x<<1$ ,右儿子是$x<<1|1$,可能会牺牲一部分空间 **这样空间复杂度就是$O(kn)$,开数组的时候,最好开$N<<3$,最少开$N<<2$** 对于build,我采用递归建树;每次查找时,将$l,r$作为参数传入给函数 ## 对于pushup和pushdown 重要的哲学来了; *此哲学已变。* **本人习惯:打上lazy标记表示,此区间内(包含此节点)的数据未更新** ~~现在改了~~ ### 正确时机: 修改时: **总结:直接返回要下放,查询子区间要下放,up前要将子区间的标记也下放** ```cpp void update(int x, int l, int r, int ql, int qr, const Mat &k) { if (ql <= l && r <= qr) { lz[x] *= k; // 打上lazy标记 down(x);// 立即下放标记 return; } down(x);// 更新子区间前先下放标记 gm; if (ql <= mid) update(ls, l, mid, ql, qr, k); else down(ls); if (mid < qr) update(rs, mid + 1, r, ql, qr, k); else down(rs); // 注意,因为之后要pushup,所以左右区间都要正确更新,即,每个区间如果不访问,要特地pushdown一下,这样pushup结果才能对; up(x); } ``` 注意,对于标记互相冲突(有先后顺序的标记),应该在进入节点时先pushdown一遍; 求解时: **总结:每个区间先pushdown,不用pushup** ```cpp Mat query(int x, int l, int r, int ql, int qr) { down(x); // 先下放标记 if (ql <= l && r <= qr) { return tre[x]; } gm; Mat ret; if (ql <= mid) ret = query(ls, l, mid, ql, qr); if (mid < qr) ret += query(rs, mid + 1, r, ql, qr); // 查询子区间 return ret; } ``` ## 对于区间修改,是直接覆盖lazy标记 ## 别瞎用else!!! ## 对于区间乘法和加法,在下放标记时,要对子区间的加法标记乘上父区间的乘法标记 ~~整体二分线段树写挂祭~~ ## 修改时,先下方标记,再递归! # 分清N,M! ## pushdown判断数组越界时,不要搞混数组大小! ## 如果选择的是无脑pushdown,务必不要下放叶节点 # 线段树优化常数 当询问完全在一个区间时,直接return,不要再合并1次; ## 线段树判定当$rs<M$成立时,这个点不一定是叶子!