{
很久以前就说过线段树的内容要写6篇了
没想到跨年了都 现在是总结的时候了
这里是以前的5篇
}
线段树总结 关键字
二分 将父亲区间均分设为儿子区间
切割 将查询区间切割为Log级别个的树上区间
概括 高效概括每个区间上的状况
合并 将不同的区间情况合并起来得到总区间的情况
二分来源于线段树的定义
了解更多关于线段树的定义和基本性质 请移步这里
这是线段树之所以能够达到Log级别的本质原因
也是二进制思想的一个应用
还记得这一个问题吧
Pku 2777 http://poj.org/problem?id=2777
我在这里 也就是介绍线段树的第一篇里 把它做为例子
不知有没有发现 这个做法的最坏情况是O(Q*L)的
这一篇以讨论这个问题为主线 总结线段树的思想
我们通过切割的思想来分析问题
我们引入一个东西叫混合颜色 查询到混合颜色就继续递归
那么 很容易想出一个数据 即所有的分支节点都是混合颜色 叶子染上黑白相间的颜色
那么查询就变成O(L)的了——必须遍历所有叶子节点
可以看到 这里线段树把查询区间切割成了L个区间 其实不如用一个线性表模拟
而实际上 我们发现树上的区间被查询区间完全包含了之后——
就不应当向下递归了 这样才是货真价实的Log2L个区间
关于线段树切割区间与保证切割性质的Lazy-Tag的内容 请移步这里
那么我们为什么要记录混合颜色这种东西呢?
是因为我们不清楚 这个区间是什么情况 这就牵涉到概括的问题了
高效概括出每个区间的情况 并且支持高效的合并运算
是我们最终完全解决这个问题的保障
为什么区间排名问题必须要在线段树的节点内加入一个平衡树?
因为我们非得知道这个区间里所有的数才能查询到某个数在这个区间的排名
为了高效维护这些数 我们就得用平衡树来组织
也就是用平衡树来概括区间信息
树状数组的本质是去除一部分节点的线段树 利用了某些合并运算的可减性
精简了某些区间 所以能够更高效的概括区间信息
回到这个问题 我们要概括包含某些颜色的区间的信息
并且支持高效的合并运算 我们可以看到 颜色最多才30种
利用一个32位长整形概括区间包含不包含各种颜色
合并运算就是Or运算 问题就可以解决了
比如一个区间含有1 2 3 5 号颜色 就存为10111=23
另一个区间含有1 3 4号颜色 就存为1101=11
合并两个区间就是含有1 2 3 4 5号颜色 即11 Or 23=10111 Or 1101=11111=31
就可以得到31这个结果 只要统计二进制中含有几个1就可以得到答案了
代码不难写 直接给出


var tt,n,t,q,x,y,z,i:longint;
l,r,c,d,ls,rs: array [ 1 ..max] of longint;
ch:char;
procedure down(x:longint);
begin
if d[x] > 0
then begin
c[x]: = d[x];
if ls[x] <> 0 then d[ls[x]]: = d[x];
if rs[x] <> 0 then d[rs[x]]: = d[x];
d[x]: = 0 ;
end ;
end ;
procedure update(x:longint);
begin
down(ls[x]); down(rs[x]);
c[x]: = c[ls[x]] or c[rs[x]];
end ;
procedure build(a,b:longint);
var x,mid:longint;
begin
inc(tt); x: = tt;
c[x]: = 1 ; d[x]: = 0 ;
l[x]: = a; r[x]: = b;
if b - a > 1
then begin
mid: = (a + b)shr 1 ;
ls[x]: = tt + 1 ; build(a,mid);
rs[x]: = tt + 1 ; build(mid,b);
end ;
end ;
procedure cover(x,a,b,c:longint);
var mid:longint;
begin
down(x);
if (a <= l[x]) and (r[x] <= b)
then d[x]: = c
else begin
mid: = (l[x] + r[x])shr 1 ;
if a < mid then cover(ls[x],a,b,c);
if b > mid then cover(rs[x],a,b,c);
update(x);
end ;
end ;
function count(x,a,b:longint):longint;
var mid:longint;
begin
down(x);
if (a <= l[x]) and (r[x] <= b)
then count: = c[x]
else begin
count: = 0 ;
mid: = (l[x] + r[x])shr 1 ;
if a < mid then count: = count or count(ls[x],a,b);
if b > mid then count: = count or count(rs[x],a,b);
end ;
end ;
function calcu(x:longint):longint;
begin
calcu: = 0 ;
while x > 0 do
begin
calcu: = calcu + x and 1 ;
x: = x shr 1 ;
end ;
end ;
begin
assign(input, ' Count.in ' ); reset(input);
assign(output, ' Count.out ' ); rewrite(output);
readln(n,t,q);
build( 0 ,n);
for i: = 1 to q do
begin
read(ch,x,y);
if x > y
then begin
z: = x; x: = y; y: = z;
end ;
if ch = ' P '
then writeln(calcu(count( 1 ,x - 1 ,y)))
else begin
read(z);
cover( 1 ,x - 1 ,y, 1 shl (z - 1 ));
end ;
readln;
end ;
close(input); close(output);
end .
关于线段树的介绍 到此告一段落
以后如果还有好玩的经典的 线段树问题 还会新写
谢谢阅读 如有不足欢迎指正!