经过一番刷题,终于用非递归线段树搞定线段树的基本问题,ZKW神牛的《统计的力量》带我入门,尽管后面一大堆都看不懂……言归正传,贴模板,顺便为以后区域赛做准备——
int M, T;
/*
M和T都是非递归线段树里面的关键变量,M的含义同《统计的力量》中的M含义相同,T是M的高度
计算公式:for (M = T = 1; M < n + 2; M <<= 1, T++);其中n是元素个数
*/
struct node
{
/*
放一个结点需要维护的信息
*/
} g[N << 2];//节点数*4,也可以先算出M最大值,然后开M+M数组省一个常数
void dn(int f)//等同push_down,不过是一次性将相关的标记下传,不像递归那样每次只下传一层
{
for (int i = T - 1; i > 0; i--)
{
int c = f >> i;
int l = c << 1, r = l ^ 1;
//这里就等同push_down了,用父亲节点c更新其左右儿子
}
}
void up(int c)//等同push_up,与其区别类比dn与push_down
{
for (c >>= 1; c; c >>= 1)
{
//等同push_up
}
}
void add(int l, int r, int d)
{
l += M - 1, r += M + 1;
dn(l),dn(r);
for (int s = l, t = r; s ^ t ^ 1; s >>= 1, t >>= 1)
{
if (~s & 1)
{//}
if (t & 1)
{//}
}
up(l),up(r);
}
int Q(int l, int r)
{
int ans = 0;
l += M - 1, r += M + 1;
dn(l),dn(r);
for (int s = l, t = r; s ^ t ^ 1; s >>= 1, t >>= 1)
{
if (~s & 1)
{//}
if (t & 1)
{//}
}
return ans;
}
理解dn与up之后,对应递归的写法,直接写非递归就可以了,进行区间操作与统计区间信息都类似代码中的add与Q,有的甚至可以只有dn没有up(比如区间染色),或者只有up没有dn(比如扫描线),经个人刷题经验,一般只有维护区间和才需要dn与up同时上,至于二维线段树,也就把楼上操作用结构体tree封装,然后……就没然后了,扫描线就普通的区间加操作而已,至于多个标记复合,要自己开脑洞我就不多说了
其实,确实,非递归能做的,递归也能做,至于理解方面,有人觉得递归容易理解,我却更容易理解非递归,可能由于百度上面关于非递归线段树代码资料太少,所以大家都去学递归的,因为确实暂时没发现递归做不到而非递归做得到的线段树问题,我学非递归也只是实在看不懂递归,但你们要是知道我10天时间就用非递归线段树解决单点更新,区间更新(区间加与区间染色),区间合并,扫描线,二维线段树等问题,同时现在打非递归线段树半小时打出来是很正常的速度(表示是二指禅而且不会盲打),你们就知道我为何对非递归线段树如此趋从了……顺便说句,我说的是打出框架,不代表立马能解决,像标记复合还是要想段时间的。
最后,再重复一次非递归的优势吧,尽管《统计的力量》已经有说了一些,至于缺点……《统计的力量 》也说的很清楚了→_→不过话说除了空间大一个常数还有啥缺点?
优点:
代码短容易打,看我半小时打出框架就知道了,当然,这点论文也有说
易调试,这点是基于我队友曾经对线段树调试调了整个比赛都调不出的经历我才敢说的,因为确实非递归要调试很容易,如果有人觉得递归线段树调试也不难,我也没法反对,毕竟我不懂
最后的最后,差点忘了,还有一份代码,可持久化线段树非递归版(尽管多了一个常数,但毕竟也AC了嘛→_→不过我觉得那是出题人没卡我,因为那常数好像不小)
//POJ2104 K-th Number
//http://poj.org/problem?id=2104
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#define mp(x,y) make_pair(x,y)
using namespace