`
从此醉
  • 浏览: 1047121 次
  • 性别: Icon_minigender_1
  • 来自: US
社区版块
存档分类
最新评论

线段树

 
阅读更多



线段树


一 什么是线段树
线段树是一棵二叉树,树中的每一个结点表示了一个区间[a,b]。每一个叶子节点表示了一个单位区间。对于每一个非叶结点所表示的结点[a,b],其左儿子表示的区间为[a,(a+b)/2],右儿子表示的区间为[(a+b)/2+1,b]

二 线段树的性质
1 线段树是建立在线段的基础上,每个结点都代表了一条线段[a,b]。长度为1的线段称为元线段。非元线段都有两个子结点,左结点代表的线段为[a,(a + b) / 2],右结点代表的线段为[((a + b) / 2)+1,b]。
2 线段树是平衡二叉树,最大深度为logN(N为线段树所标示的区间的长度)。我们知道一颗线段树的叶子节点的长度为1,那么假设高度为h,
则2^(h-1) = N , 有h = logN+1,当N无穷大的时候趋于logN。
3 假设一个区间长度为N即[1 , N],根据线段树的根节点表示[1 , N],然后将区间分成两半那么线段树最多的节点数是2N-1个。保守起见,开辟的空间为4*N。
4 线段树主要用于处理一段连续区间的插入,查找,统计,查询等操作,操作的时间复杂度为O(logN)。建立一颗线段树的时间复杂度为O(N)。

5 注意位运算的时候“+”比“>>”和“<<“高,所以应该要括号。

6 线段树是可以用来求解所有RMQ问题,所有树状数组可以解决的问题线段树都可以做。


三 线段树的模板

1 单点更新(hdu1166为例),单点更新都是更新叶子节点上面然后顺带更新根到叶子节点路径上的点。

struct Node{
 int left;
 int right;
 int sum;
};
Node node[4*MAXN];/*最多需要分配的空间*/
int number[MAXN];

//向上更新
void push_up(int pos){
 node[pos].sum = node[pos<<1].sum + node[(pos<<1)+1].sum;/*向上更新当前点的信息*/
}

/*建立空的二叉树,初始化每个营地人数为0*/
void buildTree(int left , int right , int pos){
 node[pos].left = left;
 node[pos].right = right;
 if(left == right){
 node[pos].sum = number[left];
 return;
 }
 /*递归建立子树*/
 int mid = (left+right)>>1;
 buildTree(left , mid , pos<<1);
 buildTree(mid+1 , right , (pos<<1)+1);
 push_up(pos);
}

/*单点更新*/
void update(int index , int num , int pos){
 if(node[pos].left == node[pos].right){
 node[pos].sum += num;
 return;
 }
 int mid = (node[pos].left+node[pos].right)>>1;
 if(index <= mid)
 update(index , num , pos<<1);
 else 
 update(index , num , (pos<<1)+1);
 push_up(pos);/*路径上的点都要向上更新*/
}

/*区间查询分成三种可能的区间
1 都在左儿子这个区间
2 都在右儿子这个区间
3 跨越左右儿子
*/
int query(int Left , int Right , int pos){
 if(node[pos].left == Left && node[pos].right == Right) 
 return node[pos].sum;
 int mid = (node[pos].left+node[pos].right)>>1;
 if(Right <= mid)
 return query(Left , Right , pos<<1);
 else if(Left > mid)
 return query(Left , Right , (pos<<1)+1);
 else
 return query(Left , mid , pos<<1)+query(mid+1 , Right , (pos<<1)+1);
}



2 成段更新

1 成段更新和单点更新是不同的,单点更新是要更新到叶子节点,但是对于成段更新是更新到某个区间即可,找个区间是当前需要的更新的区间的最大的子区间
2 成段更新需要维护一个“延时标记”,初始化看情况。我们先把标记放在一个大的区间,下次遇到的时候再进行向下的更新(标记传递)

代码:

//我们以区间更改和区间求和为样例
struct Node{
    int left;
    int right;
    int mark;//延时标记
    int sum;//和或者是其它的信息
};
Node node[4*MAXN];

//向上更新
void push_up(int pos){
    node[pos].sum = node[pos<<1].sum + node[(pos<<1)+1].sum;
}

//向下更新
void push_down(int pos){
    if(node[pos].mark){//如果可以继续往下更新
       node[pos<<1].mark += node[pos].mark;
       node[(pos<<1)+1].mark += node[pos].mark;
       node[pos<<1].sum += node[pos].mark*(node[pos<<1].right-node[pos<<1].left+1);
       node[(pos<<1)+1].sum += node[pos].mark*(node[(pos<<1)+1].right-node[(pos<<1)+1].left+1);
       node[pos].mark = 0;//当前点的延时标记更改为0
    }
}

//建立线段树
void buildTree(int left , int right , int pos){
    node[pos].left = left;
    node[pos].right = right;
    node[pos].mark = 0;
    if(left == right){
        node[pos].sum = num[left];
        node[pos].mark = num[left];
        return;
    }
    int mid = (left+right)>>1;
    buildTree(left , mid , pos<<1);
    buildTree(mid+1 , right , (pos<<1)+1);
    push_up(pos);//向上更新
}

//成段更新
void update(int left , int right , int value , int pos){
    if(left <= node[pos].left && right >= node[pos].right){
      node[pos].mark += value;
      node[pos].sum += value*(node[pos].right-node[pos].left+1);
      return ;
    }
    push_down(pos);//向下更新
    int mid = (node[pos].left+node[pos].right)>>1;
    if(right <= mid)
        update(left , right , value , pos<<1);
    else if(left > mid)
        update(left , right , value , (pos<<1)+1);
    else{
        update(left , mid , value , pos<<1);
        update(mid+1 , right , value , (pos<<1)+1);
    }
    push_up(pos);//向上更新
}

int query(int left , int right , int pos){
    if(node[pos].left == left && node[pos].right == right)
        return node[pos].sum;
    int mid = (node[pos].left+node[pos].right)>>1;
    push_down(pos);//这里还要进行向下更新,防止没有更新彻底
    if(right <= mid)
        return query(left , right , pos<<1);
    else if(left > mid)
        return query(left , right , (pos<<1)+1);
    else
        return query(left , mid , pos<<1) + query(mid+1 , right , (pos<<1)+1);
}

3 线段树的离散化

离散化:就是把一些离散化的点映射成一些连续的点,比如1 , 1000,300000 , 40000000可以离散化成1,2,3,4这四种点

那么区间也就可以离散成集中的点,比如有这四个区间[1,3] , [1000 , 3000] , [50000 , 600000] , [56798900 , 4546565654]l,那么我们只要对所有的值进行排序

1,3,1000,3000,50000,600000 , 5678900,4546565654 那么就可以离散成1 ,2 ,3,4,5,6,7,8.那么区间就可以变成[1,2],[3,4],[5,6],[7,8].那么这样就把相对离散的区间转化成集中的区间了,那么再利用线段树即可。




分享到:
评论

相关推荐

    acm程序设计竞赛_培训_线段树

    浙江大学_acm程序设计竞赛_培训_线段树浙江大学_acm程序设计竞赛_培训_线段树浙江大学_acm程序设计竞赛_培训_线段树浙江大学_acm程序设计竞赛_培训_线段树浙江大学_acm程序设计竞赛_培训_线段树浙江大学_acm程序设计...

    线段树的一种实现

    一种简单的线段树的实现 ,基础功能比较完善

    pascal区间线段树

    一个讲述线段树的好资料,这里主要是程序部分,希望对广大成员能够有所帮助

    线段树区间更新code

    线段树区间更新代码线段树区间更新代码线段树区间更新代码线段树区间更新代码

    几道经典线段树题目及代码

    线段树、线段树啊、线段树,线段树啊、线段树

    线段树模板

    手打了一份线段树代码,用于c++编程, 线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。 使用线段树可以快速的查找某一个节点在若干条线段中出现的...

    线段树ppt的好东东

    线段树ppt,线段树ppt,线段树ppt,树ppt,线段树ppt,线段树ppt,

    线段树解析与经典例题.ppt

    一个线段是对应于一个区间的,因此线段树也可以叫做区间树。 线段树是一棵二叉树,树中的每一个结点表示了一个区间[a,b]。每一个叶子节点表示了一个单位区间。对于每一个非叶结点所表示的结点[a,b],其左儿子表示的...

    权值线段树和主席树入门

    权值线段树和主席树入门PPT,权值线段树,顾名思义就是记录权值的线段树,普通的线段树直接以坐标为l,r建树,而权值线段树是以大小来建树,树上寸的信息是该权值的数量,而通过建树时二分从小到大的性质,可以用这...

    线段树初步(C++)

    讲解线段树基本应用,适合初学者下载使用!

    线段树完整版

    ACM学习中 涉及到线段树的代码分析模板

    线段树六题

    著名的线段树六题 囊括线段树整个知识点, 十分实用 当年凭借此六题, NOIP+NOI秒杀线段树题

    线段树学习ppt

    线段树学习ppt

    线段树模板 zoj1128

    本资源是对线段树操作比较完整的操作,包括线段树的动态插入,动态删除和维护,可以查询区段的最大值,最小值,完成线段树的基本操作。

    线段树PPT两个,所有常规用法

    线段树PPT,所有常规用法线段树PPT,所有常规用法线段树PPT,所有常规用法线段树PPT,所有常规用法

    线段树,针对一组数的的统计十分方便

    线段树,针对一组数的的统计十分方便。 线段树,针对一组数的的统计十分方便。 线段树,针对一组数的的统计十分方便。 线段树,针对一组数的的统计十分方便。 线段树,针对一组数的的统计十分方便。

    线段树.pdf

    线段树完全版,涉及到线段树的所有用法。 包括单点更新(增减,替换),区间求和,区间最值。 区间求最大值的位置。 成段更新(延迟标记,增减)。 离散化 扫描线

    线段树 go语言实现

    go语言实现的线段树源码, 可以直接运行, 代码简洁清晰, 快去下载吧

    线段树高级数据结构实现

    线段树点更新

    线段树(一)

    线段树(一)

Global site tag (gtag.js) - Google Analytics