线段树(Interval Tree)
实际上还是称为区间树更好理解一些。树:是一棵树,而且是一棵二叉树。
线段:树上的每个节点对应于一个线段(还是叫“区间”更容易理解,区间的起点和终点通常为整数)
同一层的节点所代表的区间,相互不会重叠。同一层节点所代表的区间,加起来是个连续的区间。
叶子节点的区间是单位长度,不能再分了。
线段树的深度不超过log2(n)+1(向上取整,n是根节点对应区间的长度)。
线段树上,任意一个区间被分解后得到的“终止节点”数目都是log(n)量级。
线段树上更新叶子节点和进行区间分解时间复杂度都是O(log(n))的。
这些结论为线段树能在O(log(n))的时间内完成插入数据,更新数据、查找、统计等工作,提供了理论依据。
非叶子节点可以放和,最值,类型,状态,空间容量(一般用于离散化之后)。
1.结构体定义
struct node
{
int left,right;
int w;
int mid()
{
return (left+right)/2;
}
}node[max*3]; //max视情况而定
关于max,
有时需要进行离散化处理,一般是qsort后开一个很大的数组储存i++,进行映射;
有时候可能并不是题目给的那个明显的n,而是需要输入update的个数; 例如杭电2795;
总而言之,这一切的目的都是把max减到最小;
2.建树
void build(int i,int l,int r)
{
node[i].left=l;node[i].right=r;
node[i].w=0; //对节点node[i]初始化;
if(l==r) //这里可以对叶子节点进行附值;
return ; //也可以用一个father数组记录每个node[i].left的i;
build(i*2,l,(l+r)/2);
build(i*2+1,(l+r)/2+1,r);
}
void update(int i,int l,int r,int c)
{
if(node[i].left==l&&node[i].right==r)
node[i].w+=c;
if(node[i].mid()>=r) update(i*2,l,r,c);
else if(node[i].mid()<l) update(i*2+1,l,r,c);
else
{
update(i*2,l,node[i].mid(),c);
update(i*2+1,node[i]+1,r,c);
}
}
build和update以void格式时都有一个回溯功能,可以用起来减少步骤;例如build用于区域求和;update用于区段的更新;
if(node[i+i].w&&node[i+i+1].w) node[i].w=1;
不要一更新就更新到叶子节点,那样更新效率最坏就可能变成O(n)的了!有些甚至要把信息全部累计在非叶子节点,最后从某个叶子节点返回上去进行统计;
int query(int i,int l,int r)
{
if(node[i].left==l&&node[i].right==r) return node[i].w;
if(node[i].mid()>=r) return query(i*2,l,r);
else if(node[i].mid()<l) return query(i*2+1,l,r);
else return query(i*2,l,node[i].mid())+query(i*2+1,node[i].mid()+1,l);
}
5.其他
有些关于先后问题例如排队poj2828,覆盖poj2825,可以先从后而前考虑,因为后面的都是可以确定的;
当给你的某个值n比较小的时候,可能是给你遍历的可能性;
线段树还有区间合并和lazy标记需要拓展。
当然很多难题关于线段树和其他知识点(例如数论)的结合就只能凭能力了。
下面网址有点资料还不错。
http://acm.pku.edu.cn/summerschool/pku_acm_train.htm