ACM竞赛算法之线段树

线段树是一个很重要的数据结构,而且在算法竞赛中用处也十分巨大,但很多人往往认为线段树是一个算法,可以完成某些功能,但是实际上完全可以把它看成是一个容器,用来执行的操作可以按照需求修改


首先思考如下问题:
思考一、现在如果有一个长度为n的序列,有m次操作,操作中包含区间求和,某点修改,并且n和m特别大
思考二、现在给你一个长度为n的序列,有m次操作,操作中仅包含求区间内最大值或最小值的问题,n和m特别大


这两个思考题看起来都很容易,暴力都可以解决问题,但是如果n和m特别大的时候,就会出现时间超限的问题,所以使用线段树进行维护就会节省时间,但要消耗内存,适当的开结点数量才可以完美的AC,如果开的结点数量少的话就会出现运行错误,如果开的结点数量大 的话又会出现内存超限,所以经过计算及测试后,确定将结点个数开成序列长度的四倍刚刚好


学习线段树,首先要知道线段树是一棵二叉树,如果从左至右从上至下给每个结点进行编号的话,如果双亲结点的标号为root,那么它的左孩子的编号就为root*2,右孩子的标号为root*2,这就是双亲结点与孩子结点的关系,也是线段树结点之间的关系,还要确定结点内需要存储记录的东西

int a[1000];//存储数据
int sum=0;//计算和
struct st
{//线段树结点信息
    int l,r,len,lazy,sum;
    //左端点、右端点、区间长度、懒惰标记、区间和
}tree[4005];//四倍序列长度

建树操作
线段树的建树方法也十分重要,一般采用二分回溯的方法建树及处理,先进行建树,在组建结点全部完成后再回溯执行向上更新
操作

void push_up(int root)
{//线段树向上更新,即双亲结点的和等于孩子结点之和
    tree[root].sum=tree[root<<1].sum+tree[root<<1|1].sum;
}
void bulid(int l,int r,int root)
{//建树操作
    tree[root].l=l;//左端点
    tree[root].r=r;//右端点
    tree[root].len=r-l+1;//区间长度
    tree[root].lazy=0;//lazy标记
    if(l==r){//叶子结点存值
        tree[root].sum=a[l];
        return ;
    }
    int mid=(l+r)>>1;//找寻中间值
    bulid(l,mid,root<<1);//建立左子树
    bulid(mid+1,r,root<<1|1);//建立右子树
    push_up(root);//向上更新线段树
}

在数据结构中,有两个十分重要的操作,那就是查询和更新,线段树也是这样的,查询和更新也很重要
线段树更新操作有两种,一种是单点更新,一种是区间更新
查询操作也对应的有两种,但是它们之间的联系比较紧密


线段树单点更新
线段树单点更新还是比较容易理解的,找到线段树中某一个叶子结点进行修改,然后再更新部分结点信息即可

void update_point(int i,int x,int root)
{//单点更新操作,某个点增加x
    if(tree[root].l==tree[root].r&&tree[root].l==i){
        //找到修改位置,进行修改
        tree[root].sum+=x;
        return ;
    }
    int mid=(tree[root].l+tree[root].r)>>1;
    if(i<=mid) update_point(l,mid,root<<1);
    //需要更新结点在当前结点的左子树部分
    else update_point(mid+1,r,root<<1|1);
    //需要更新结点在当前结点的右子树部分
    push_up(root);
}

线段树区间更新
线段树区间更新相对于线段树单点更新比较难,简单暴力也可以实现区间更新,暴力每一个更新的点进行单点更新,但是这样往往得到的答案都是TLE,所以就需要使用一个懒惰标记lazy进行暂停更新,如果需要查询到结点的话再向下更新,否则不更新,这样就会节省时间

void push_down(int root)
{//线段树向下更新,即lazy更新
    tree[root<<1].sum+=tree[root<<1].len*tree[root<<1].lazy;
    tree[root<<1|1].sum+=tree[root<<1|1].len*tree[root<<1|1].lazy;
    //更新孩子结点sum值操作
    tree[root<<1].lazy+=tree[root].lazy;
    tree[root<<1|1].lazy+=tree[root].lazy;
    //更新孩子结点lazy操作
    tree[root].lazy=0;
    //双亲结点lazy清零
}
void update_interval(int l,int r,int c,int root)
{//线段树区间更新操作
    if(l<=tree[root].l&&r>=tree[root].r){
        //如果要更新区间包含当前结点区间
        tree[root].sum+=tree[root].len*c;
        //修改当前结点和
        tree[root].lazy+=c;
        //修改当前结点lazy标记
        return ;
    }
    if(tree[root].lazy) push_down(root);
    //判断当前结点的lazy标记是否存在,存在即向下更新
    int mid=(tree[root].l+tree[root].r)>>1;
    if(l<=mid) update_interval(l,r,c,root<<1);
    //如果成立,表示更新区间部分在当前结点的左子树中
    if(r>mid) update_interval(l,r,c,root<<1|1);
    //如果成立,表示更新区间部分在当前结点的右子树中
    push_up(root);
}

线段树查询操作
线段树查询是查询区间内区间和、最大最小值问题,也可以查询其他的东西,主要是看解决什么问题,这里我写了两段查询的代码,分别为单点更新查询、区间更新查询的两段代码,代码中注释很多。

void query_point(int l,int r,int root)
{//线段树单点更新查询操作
    if(tree[root].l==l&&tree[root].r==r){
        //查到符合要求区间,加和
        sum+=tree[root].sum;
        return ;
    }
    /*if(tree[root].lazy) push_down(root);
    加上这行即可完成区间更新操作*/
    root<<=1;
    if(l<=tree[root].r){
        if(r<=tree[root].r)
            //所求区间在结点左子树中
            query_point(l,r,root);
        else
            //所求区间部分在结点左子树中
            query_point(l,tree[root].r,root);
    }
    root++;
    if(r>=tree[root].l){
        if(l<=tree[root].l)
            //所求区间在结点右子树中
            query_point(l,r,root);
        else
            //所求区间部分在结点右子树中
            query_point(tree[root].l,r,root);
    }
}
int query_interval(int l,int r,int root)
{//线段树区间更新查询操作
    if(l<=tree[root].l&&r>=tree[root].r){
        //查询区间包含结点区间
        return tree[root].sum;
    }
    if(tree[root].lazy) push_down(root);
    //判断是否可以向下更新
    int mid=(tree[root].l+tree[root].r)>>1;
    int ans=0;//计算值
    if(l<=mid) ans+=query(l,r,root<<1);
    //查询区间部分在左子树部分
    if(r>mid) ans+=query(l,r,root<<1|1);
    //查询区间部分在右子树部分
    return ans;
}

以上只是线段树的基础知识,建议多多理解线段树的思想及实现过程,ACM竞赛中几乎没有模板,思想要比代码重要一些
如有错误,还请批评指正

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
时间复杂度(渐近时间复杂度的严格定义,NP问题,时间复杂度的分析方法,主定理)   排序算法(平方排序算法的应用,Shell排序,快速排序,归并排序,时间复杂度下界,三种线性时间排  序,外部排序)   数论(整除,集合论,关系,素数,进位制,辗转相除,扩展的辗转相除,同余运算,解线性同余方程,中国剩余定理) 指针(链表,搜索判重,邻接表,开散列,二叉树的表示,多叉树的表示) 按位运算(and,or,xor,shl,shr,一些应用) 图论(图论模型的建立,平面图,欧拉公式与五色定理,求强连通分量,求割点和桥,欧拉回路,AOV问题,AOE问题,最小生成树的三种算法,最短路的三种算法,标号法,差分约束系统,验证二分图,Konig定理,匈牙利算法,KM算法,稳定婚姻系统,最大流算法,最小割最大流定理,最小费用最大流算法) 计算几何(平面解几及其应用,向量,点积及其应用,叉积及其应用,半平面相交,求点集的凸包,最近点对问题,凸多边形的交,离散化与扫描) 数据结构(广度优先搜索,验证括号匹配,表达式计算,递归的编译,Hash表,分段Hash,并查集,Tarjan算法,二叉堆,左偏树,二斜堆,二项堆,二叉查找树,红黑树,AVL平衡树,Treap,Splay,静态二叉查找树,2-d树,线段树,二维线段树,矩形树,Trie树,块状链表) 组合数(排列与组合,鸽笼原理,容斥原理,递推,Fibonacci数列,Catalan数列,Stirling数,差分序列,生成函数,置换,Polya原理) 概率论(简单概率,条件概率,Bayes定理,期望值) 矩阵(矩阵的概念和运算,二分求解线性递推方程,多米诺骨牌棋盘覆盖方案数,高斯消元) 字符串处理(KMP,后缀树,有限状态自动机,Huffman编码,简单密码) 动态规划(单调队列,凸完全单调性,树型动规,多叉转二叉,状态压缩类动规,四边形不等式) 博奕论(Nim取子游戏,博弈树,Shannon开关游戏) 搜索(A*,ID,IDA*,随机调整,遗传算法

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值