python_17_线段树算法

1 线段树

线段树:又叫区间修改树,区间的修改、更新、查询一些东西,怎么修改、更新、查询的比较快(logO)

1.1 概念

arr[0] 不用,剩下全copy
arr不管有多大,都需要构建出一棵满二叉树
void add(L,R,V,arr)        在L....R范围上统一增加V
void updata(L,R,V,arr)     在L....R上统一变成V
void getsum(L,R,arr)       在L...R范围上返回累加和

1.2 要求

arr不管有多大,都需要构建出一棵满二叉树
数据量N正好是2的n次方要2N空间,任意N需要准备4N空间,2的n次方+1最不省空间。
sum累加和作为线段树
在这里插入图片描述
arr如果长度为6,在后面补0就可以补成满二叉树了。
把arr[3,2,5,7]转化为线段树,储存的数组是什么样子?
在这里插入图片描述
i位置的左孩子是2i,右孩子是2i+1,arr[0]不用。

1.3 代码

1.2.1 代码实现细节

在这里插入图片描述
在这里插入图片描述

300-600之间加5
在这里插入图片描述
在这里插入图片描述
任务执行完(执行到1-1,2-2那一层),最上面lazy会归0

1.2.2 总体代码

class SegmentTree:
    def __init__(self,origin):
        # arr[]
        # 为原序列的信息从0开始,但在arr里是从1开始的
        # sum[]
        # 模拟线段树维护区间和
        # lazy[]
        # 为累加和懒惰标记
        # change[]
        # 为更新的值
        # update[]
        # 为更新慵懒标记
        self.MAXN = len(origin) + 1
        self.arr = [0] * self.MAXN  # arr[0] 不用,一个数组上的所有信息
        for i in range(1,self.MAXN):
            self.arr[i] = origin[i-1]
        self.sum = [0] * (self.MAXN << 2)        # sum准备4倍,用来支持脑补概念中,某一个范围的累加和信息
        self.lazy = [0] * (self.MAXN << 2)       # lazy准备4倍,用来支持脑补概念中,某一个范围沒有往下傳遞的纍加任務
        # 下面两个搭配 组合使用懒更新信息
        self.change = [0] * (self.MAXN << 2)    # 用来支持脑补概念中,某一个范围有没有更新操作的任务
        self.is_update = [False] * (self.MAXN << 2)   # 用来支持脑补概念中,某一个范围更新任务,更新成了什么

    # 在初始化阶段,先把sum数组,填好
    # 在arr[l~r]范围上,去build,1~N,
    # rt : 这个范围在sum中的下标
    def build(self,l,r,rt):
        if l == r:
            self.sum[rt] = self.arr[l]
            return
        mid = (l + r) >> 1
        self.build(l, mid, rt << 1)    # 左右树信息填好
        self.build(mid + 1, r, rt << 1 | 1)
        self.pushUp(rt)

    def pushUp(self,rt):  # 汇总,累加和信息
        self.sum[rt] = self.sum[rt << 1] + self.sum[rt << 1 | 1]

    # L ~ R   所有的值变成C
    # l~r  rt
    # L...R表示任务范围,所有的值累加上C
    # l,r 实际的范围
    # rt 去哪找l,r范围上的信息
    def add(self,L,R,C,l,r,rt):
        # 任务的范围彻底覆盖了,当前表达的范围
        if L <= l and r <= R:
            self.sum[rt] += C * (r - l + 1)   # r - l + 1 实际数的数量
            self.lazy[rt] += C   # 多任务可调整累加和
            return

        # 任务并没有把l...r全包住
        # 要把当前任务往下发
        # 任务L...R 没有把本身表达范围l,r彻底包住
        # l...mid 左边范围,下标是 rt*2     mid+1...r  rt*2 + 1
        mid = (l + r) >> 1  # 这是除2的意思
        # 下发之前的lazy add任务
        self.pushDown(rt,mid - l + 1,r - mid)
        # L ~ R
        # 左孩子是否需要接到任务
        if L <= mid:
            self.add(L,R,C,l,mid,rt << 1)
        # 右孩子是否需要接到任务
        if R > mid:
            self.add(L,R,C,mid + 1,r,rt << 1 | 1) # 除2+1
        # 左右孩子做完任务后,我更新我的sum信息
        self.pushUp(rt)

    # 1~6 累加和是多少? 1~8 rt是哪个位置
    def query(self,L,R,l,r,rt):
        if L <= l and r <= R:
            return self.sum[rt]
        mid = (l + r) >> 1
        self.pushDown(rt, mid - l + 1, r - mid)
        ans = 0
        if L <= mid:
            ans += self.query(L, R, l, mid, rt << 1)
        if R > mid:
            ans += self.query(L, R, mid + 1, r, rt << 1 | 1)
        return ans

    # L ~ R   所有的值变成C
    # l~r  rt
    def update(self,L,R,C,l,r,rt):   # 更新方法
        if L <= l and r <= R:
            self.is_update[rt] = True  # 有没有发生update操作,发生了就是True
            self.change[rt] = C     # 所有数
            self.sum[rt] = C * (r - l + 1)
            self.lazy[rt] = 0       # lazy 东西全取消
            return

        # 当前任务躲不掉,无法懒更新,要往下发
        mid = (l + r) >> 1
        self.pushDown(rt, mid - l + 1, r - mid)
        if L <= mid:    # 更新左边
            self.update(L, R, C, l, mid, rt << 1)

        if R > mid:     # 更新右边
            self.update(L, R, C, mid + 1, r, rt << 1 | 1)
        self.pushUp(rt)    # 汇总


    # 之前的,所有懒增加,和懒更新,从父范围,发给左右两个子范围
    # 分发策略是什么
    # ln表示左子树元素结点个数,rn表示右子树结点个数
    def pushDown(self,rt,ln,rn):
        if self.is_update[rt]:
            self.is_update[rt << 1] = True
            self.is_update[rt << 1 | 1] = True
            self.change[rt << 1] = self.change[rt]   # 以父节点为准
            self.change[rt << 1 | 1] = self.change[rt]
            self.lazy[rt << 1] = 0     # 累加和全部清空
            self.lazy[rt << 1 | 1] = 0
            self.sum[rt << 1] = self.change[rt] * ln    # 左侧累加和 = 左侧节点的个数*当前节点更新值
            self.sum[rt << 1 | 1] = self.change[rt] * rn
            self.is_update[rt] = False    # 任务分发完
        if self.lazy[rt] != 0:
            # 累加是因为左右孩子还要懒信息
            self.lazy[rt << 1] += self.lazy[rt]
            self.sum[rt << 1] += self.lazy[rt] * ln
            self.lazy[rt << 1 | 1] += self.lazy[rt]
            self.sum[rt << 1 | 1] += self.lazy[rt] * rn   # rn表示几个数
            self.lazy[rt] = 0

origin = [2,1,1,2,3,4,5]
seg = SegmentTree(origin)
S = 1  # 整个区间的开始位置,规定从1开始,不从0开始 -> 固定
N = len(origin)  # 整个区间的结束位置,规定能到N,不是N-1 -> 固定
root = 1  # 整棵树的头节点位置,规定是1,不是0 -> 固定
L = 1  # 操作区间的开始位置 -> 可变
R = 2  # 操作区间的结束位置 -> 可变
C = 5  # 要加的数字或者要更新的数字 -> 可变
# 区间生成,必须在[S,N]整个范围上build
seg.build(S, N, root)
# 区间修改,可以改变L、R和C的值,其他值不可改变
seg.add(L, R, C, S, N, root)
# 区间更新,可以改变L、R和C的值,其他值不可改变
seg.update(L, R, C, S, N, root)
# 区间查询,可以改变L和R的值,其他值不可改变
total_sum = seg.query(1,4, S, N, root)
print(total_sum)


1.4 实例应用

在这里插入图片描述
max作为线段树
在这里插入图片描述
在这里插入图片描述

什么时候可以用线段树做题,在一个区间,想要增加值或者更新值,父亲节点信息可以由左数右树加工得到,而且这个信息不需要调用左右树状况情况下,所有这样的查询都可以用线段树,最大最小累加和都可以。

在这里插入图片描述
求出现次数最多的值,不能用线段树。
在这里插入图片描述

# positions表示一个一个落方块
# [2,7]  2位置开始7位置结束 -> 左边界是2,右边界是2+7 -1
# [3,10]  左边界3 ,右边界 3+10 -1


class SegmentTree:
    def __init__(self,N):
        N = N + 1
        # 这里不需要有数组,1~N认为0就行
        self.max = [0] * (N << 2)  # 最大值范围
        self.change = [0] * (N << 2)  # 下面两个懒信息
        self.is_update = [0] * (N << 2)

    def pushUp(self,rt):   # 汇总
        self.max[rt] = max(self.max[rt << 1],self.max[rt << 1 | 1])

    # ln表示左子树元素节点个数,rn表示右子树节点个数
    def pushDown(self,rt,ln,rn):
        if self.is_update[rt]:   # 若父节点有懒信息
            self.is_update[rt << 1] = True       # 左孩子收到懒信息
            self.is_update[rt << 1 | 1] = True   # 右孩子收到懒信息
            self.change[rt << 1] = self.change[rt]   # 懒的东西和父一样
            self.change[rt << 1 | 1] = self.change[rt]
            # 左、右范围刷成此时的值
            self.max[rt << 1] = self.change[rt]
            self.max[rt << 1 | 1] = self.change[rt]
            self.is_update[rt] = False   # 父信息不懒了

    def update(self,L,R,C,l,r,rt):
        if L <= l and r <= R:
            self.is_update[rt] = True
            self.change[rt] = C
            self.max[rt] = C
            return

        mid = (l + r) >> 1
        self.pushDown(rt, mid - l + 1, r - mid)
        if L <= mid:
            self.update(L, R, C, l, mid, rt << 1)
        if R > mid:
            self.update(L, R, C, mid + 1, r, rt << 1 | 1)
        self.pushUp(rt)

    def query(self,L,R,l,r,rt):
        if L <= l and r <= R:
            return self.max[rt]
        mid = (l + r) >> 1
        self.pushDown(rt,mid - l +1,r - mid)
        left = 0
        right = 0
        if L <= mid:
            left = self.query(L,R,l,mid,rt << 1)
        if R > mid:
            right = self.query(L, R, mid + 1, r, rt << 1 | 1)
        return max(left,right)



# positions是二维数组,[2,5]  表示在2位置下落的长方形,边长为5
def index(positions):
    pos = []
    for arr in positions:
        pos.append(arr[0])      # 左边界
        pos.append(arr[0] + arr[1] - 1)   # 右边界
    pos.sort()     # 所有位置都在有序表内,从小到大排好序
    map = {}
    count = 0      # 线段树所对应的标号,标好  207位置标志1,208位置标志2,所有边界混在一起排序
    for index in pos:
        count += 1
        map[index] = count
    return map

def indexx(positions):
    map = {}  # 创建一个空字典
    for i in range(len(positions)):
        map[positions[i]] = i  # 将 positions 列表中的元素作为键,索引作为值存入字典
    return map

def fallingSquares(positions):
    # 100 -> 1  306 -> 2   403 - > 3 位置对应编号
    # [100,403] 这就是把 1~3范围上做修改,这就是标志作用
    # 拿编号好的东西去修改
    map = indexx(positions)
    N = len(map)
    segmentTree = SegmentTree(N)
    is_max = 0
    res = []
    # 每落一个正方形,收集一下,所有东西组成的图像,最高高度是什么
    for arr in positions:      # 遇到的任何一个正方形
        L = map.get(arr[0])    # 左边界,取出来对应的编号
        R = map.get(arr[0] + arr[1] - 1)    # 右边界
        # L,R范围上最大值求出来,再加上该方块高度
        height = segmentTree.query(L,R,1,N,1) + arr[1]
        is_max = max(is_max,height)  # 总高度pk
        res.append(is_max)    # 加入答案
        # L,R 更新此时最高的高度
        segmentTree.update(L,R,height,1,N,1)
    return res










1.5 实例应用2

有1-N这么多房子,有17种颜色,想实现L…R刷成一种颜色,调用随机范围刷,还可以查询L…R颜色有多少种

2 一些问题讲解

2.1 线段重合问题

在这里插入图片描述
暴力解:

def maxCover1(lines):
    mins = float("-inf")
    maxs = float("inf")
    for i in range(len(lines)):
        mins = min(mins,lines[i][0]) # [2][6] 线段长度2-6
        maxs = max(maxs,lines[i][i])
    cover = 0
    for p in range(mins + 0.5,maxs):
        cur = 0
        for i in range(len(lines)):
            if lines[i][0] < p and lines[i][1] > p:  # 有没有盖住p
                cur += 1
        cover = max(cover,cur) 
    return cover

线段树:

# 线段树
class Line:
    def __init__(self,s,e):
        self.start = s
        self.end = e
    def __lt__(self,other):
        return self.start < other.start


def maxCover2(m):
    lines = []
    for i in range(len(m)):
        lines.append(Line(m[i][0],m[i][1]))
    lines.sort()
    heap = []  # 优先级队列,结尾谁小谁在上面
    maxs = 0
    for i in range(len(lines)):
        # 线段不为空 且 线段顶部的end <= 线段开始值,影响不到了,弹出
        while heap and heap[0].end <= lines[i].start:
            heap.pop(0)
        heap.append(lines[i])  # 把自己的线段加入到小根堆里去
        heap = sorted(lines, key=lambda x: x.end)
        maxs = max(maxs,len(heap))
    return maxs

2.2

哪些矩形重合在一起的多
写接口转成这么表达:

import bisect  # 插入有序
class Rectangle:
    def __init__(self,up,down,left,right):
        # 最上边界,最下边界,最左边界,最右边界
        self.up = up
        self.down = down
        self.left = left
        self.right = right

    def insert_sorted_by_right(item, param_list):
        index = bisect.bisect_left([x.right for x in param_list], item.right)
        param_list.insert(index, item)



def maxCover(recs): # resc 是矩形类型
    if recs is None or len(recs) == 0:
        return 0
    # 根据down(底)排序,最小的down在上
    recs = sorted(recs,key=lambda x:x.down)  # 以down排序
    # 可能会对当前底边的公共区域,产生影响矩形
    leftOrdered = []   # 有序表表达,从左到右依次考虑边界
    ans = 0
    # O(N)
    for i in range(len(recs)):
        curDown = recs[i].down
        index = i
        while recs[index].down == curDown:
            leftOrdered.append(recs[index])  # O(lonN)
            index += 1
        leftOrdered = sorted(leftOrdered,key=lambda x:x.left)   # 以左排序
        i = index
        # O(N)  list是不是有一些顶<=底的矩形,不会影响就移除
        removeLowerOnCurDown(leftOrdered,curDown)   # 最大线段重合问题
        # 维持了右边界排序的容器
        rightOrdered = [] # 有序表
        for rec in leftOrdered:   # O(N)
            removeLeftOnCurLeft(rightOrdered,rec.left)
            Rectangle.insert_sorted_by_right(rec,rightOrdered)
            ans = max(ans,len(rightOrdered))

    return ans

def removeLowerOnCurDown(set,curDown):
    removes = []
    for rec in set:
        if rec.up <= curDown:
            removes.append(rec)
    for rec in removes:
        set.remove(rec)

def removeLeftOnCurLeft(rightOrdered,curLef):
    removes = []
    for rec in rightOrdered:
        if rec.right > curLef:
            break
        removes.append(rec)
    for rec in removes:
        rightOrdered.remove(rec)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值