[线段树]深入理解:线段树的构建和分解方法

如果还不了解基本的线段树,请点击这里查看。


——线段树的构造,实际上是利用了二分的方法。每次在构造相应区间时,需要按照二分的规则来继续分解,并构造区间内的子区间,存成一个新的节点,并以此保留新的信息。构造整棵线段树时,需要把每个节点分解,直到所有的节点长度缩减为1时才可以停止。


——线段树的分解,遵循的规则和线段树的构造是类似的。不同之处在于:分解的规则就是:如果有某个节点的区间完全属于待分解区间,则该节点为终止节点,不再继续往下分解。所有终止的节点所代表的区间都相互不重叠,且加在一起就恰好等于整个待分解的区间。


线段树的构建:

function 以节点v为根建树、v对应的区间为L,R

{

对节点进行初始化,插入相应的节点基本信息

if(L != R)

{

以v的左孩子为根建树,区间为L,(L+R)/2

以v的右孩子为根建树,区间为(L+R)/2+1,R

}

}

因此,建树的时间复杂度为O(n),n是根节点对应的区间长度。


线段树的更新:

function 以节点为v,插入第i个位置的,其值为val

{

if(插入位置在线段树的区间内)

更新相应的节点

if (插入位置在线段树区间的左半边)

{ 以v的左孩子为根继续更新节点,其位置为i,节点为val }

else if (插入位置在线段树区间的右半边)

{ 以v的右孩子为根继续更新节点,其位置为i,节点为val }

}


线段树的查询:

function 以根节点为v,查询的左节点为L,右节点为R

{

if (查询的节点就是当前树的左右节点)

        返回查询信息,结束查询

else

{

取mid = (L+R)/2

查询的区间与 [L,mid]或[mid+1,R]哪个有交集,就进入哪个区间进行进一步查询。

}

}


用线段树解题,关键是要想清楚每个节点要存哪些信息(当然区间的起点终点,以及左右子节点的指针是必须的),以及这些信息如何高效更新,维护,查询。不要一更新就更新到叶子节点,那样更新效率最坏就可能变成O(n)的了。


const int MAXN = 200050;
const int INF = 0x3f3f3f3f;
struct node
{
    int L,R;
    int sum;
};

node tree[MAXN * 3];

void build(int root,int L,int R)
{
    tree[root].L = L;
    tree[root].R = R;
    tree[root].sum = 0;
    if (L != R)
    {
        build(root * 2 +1,L,(L+R)/2);
        build(root * 2 +2,(L+R)/2+1,R);
    }
}
void update(int root,int i,int val) // 第i个数字,其值为val,插入.
{
    if (i>=tree[root].L && i<=tree[root].R)
        tree[root].sum += val;
    if (i <= (tree[root].L + tree[root].R)/2 )
    {
        update(2*root+1,i,val);
    }
    else
    {
        update(2*root+2,i,val);
    }
}
int sum;
void querySum(int root,int l,int r)
{
    cout< (tree[root].L+tree[root].R)/2)
        querySum(root*2+2,l,r);
    else
    {
        querySum(root*2+1,l,(tree[root].L+tree[root].R)/2);
        querySum(root*2+2,(tree[root].L+tree[root].R)/2+1,r);
    }
}




你可能感兴趣的:(1,数据结构,————ACM训练————)