acm暑期学校第一天 整理

来源:互联网 发布:excel 数据库 编辑:程序博客网 时间:2024/05/17 03:12

线段树

实际上还是称为区间树更好理解一些。
 线段:树上的每个节点对应于一个线段(还是叫“区间”更容易理解,区间的起点和终点通常为整数)
不会重叠。同一层节点所代表的区间,加起来是个连续的区间。
 叶子节点的区间是单位长度,不能再分了。

线段树是一棵二叉树,树中的每一个结点表示了一个区间[a,b]。a,b通常是整数。每一个叶子节点表示了一个单位区(长度为1)。对于每一个非叶结点所表示的结点[a,b],其左儿子表示的区间为[a,(a+b)/2],右儿子表示的区间[(a+b)/2+1,b](除法去尾取整)。

                           [1,9]

              [1,5]           [6,9]       [1,3]    [4,5]     [6,7]    [8,9]
    [1,2]   3   4    5    6   7    8    9    1    2

深度为log2(b-a+1) +1 

若一个节点对应的区间是[a,b],则其子节点对应的区间分别是[a,(a+b)/2]和[ (a+b)/2+1,b] (除法去尾取整)

线段树的构建
function 以节点v为根建树、v对应区间为[l,r]
{
对节点v初始化
if (l!=r)
{
以v的左孩子为根建树、区间为[l,(l+r)/2]
以v的右孩子为根建树、区间为[(l+r)/2+1,r]
}
}
建树的时间复杂度是O(n) n为根节点对应的区间长度


线段树应用举例
 给你一个数的序列A1A2……An。 并且可能多次进行下列两个操作:
 1、对序列里面的某个数进行加减
 2、询问这个序列里面任意一个连续的子序列AiAi+1……Aj的和是多少。
 希望第2个操作每次能在log(n)时间内完成

poj 3264,3468,2528,1151,3321


树状数组

对于序列a,我们设一个数组C
C[i] = a[i – 2k+ 1] + … + a[i]
k为i在二进制下末尾0的个数
2k就是i 保留最右边的1,其余位全变0
i从1开始算
C即为a的树状数组


用lowbit(x)表示x对应的2k,
lowbit(x) = x&(-x) 
lowbit(x) 实际上就是x的二进制表示形式
留下最右边的1,其他位都变成0
C[i] = a[i-lowbit(i)+1] + …+ a[i]
C包含哪些项看上去没有规律


树状数组的好处在于能快速求任意区间的和
a[i] + a[i+1] + … + a[j]

设sum(k) = a[1]+a[2]+…+a[k]
则a[i] + a[i+1] + … + a[j] = sum(j)-sum(i-1)

sum(k) = C[n1]+C[n2] + …+ C[nm]
其中 nm= k
ni-1 = ni- lowbit(ni)


有了树状数组,sum(k)就能在O(logN)时间内求出,N是a数组元素个数。而且更新一个a的元素所花的时间也是O(logN)的(因为a更新了C也得更新)。

当数组中的元素有变更时,树状数组就发挥它的优势了,算法如下(修改为给某个节点 i 加上 x ):

         (1)当 i<=n 时,执行下一步;否则的话,算法结束;

         (2)ci=ci+x ,i=i+lowbit(i)(在 i 的二进制表示的最后加零),返回第一步。

          代码实现:

void change(int i,int x)
{
     while(i<=n)
     {
          c[i]=c[i]+x;
          i=i+lowbit(i);
     }
}

poj3321,1195,2155,2182,2352,1177,3667,3067




0 0
原创粉丝点击