[python刷题模板] 倍增BinaryLifting

[python刷题模板] 倍增BinaryLifting

    • [一、 算法&数据结构](#一、 算法&数据结构)
      • [1. 描述](#1. 描述)
      • [2. 复杂度分析](#2. 复杂度分析)
      • [3. 常见应用](#3. 常见应用)
      • [4. 常用优化](#4. 常用优化)
    • [二、 模板代码](#二、 模板代码)
      • [1. 1483. 树节点的第 K 个祖先(LCA前置模板)](#1. 1483. 树节点的第 K 个祖先(LCA前置模板))
      • [2. 在有限状态中转移(957. N 天后的牢房)](#2. 在有限状态中转移(957. N 天后的牢房))
      • [3. 需要计算区间贡献(2836. 在传球游戏中最大化函数值)](#3. 需要计算区间贡献(2836. 在传球游戏中最大化函数值))
    • 三、其他
    • 四、更多例题
    • 五、参考链接

一、 算法&数据结构

1. 描述

倍增是一种优化复杂度的思想,通过把区间压缩到二进制下标的方式,可以大量的合并信息。这也要求区域内的贡献通常是均匀的。
查询时,把路径用二进制分解,那么就可以快速到达目标。
通常,由于初始化需要nlogn的时间,需求应该是离线的。
  • 定义pa[i][j]为节点i的距离2^j位置的节点(可能还需要一个f数组记录这个路径上的贡献值)。
  • 初始化pa[i][0]为i下一个(相邻的)节点,在lca上就是父节点,可能需要dfs之类来初始化这个。
  • 转移:
python 复制代码
for i in range(m-1):  # 外层优先遍历步数2^i
	for u in range(n):   # 内层转移节点
		p=pa[u][i];  # 当前节点的父节点
		pa[u][i + 1] = pa[p][i];  # 那么从u跨两个2^i步就到达p的2^i步
		f[u][i+1] = f[u][i]+f[p][i]  # 从u跨两个2^i,就是从u夸一次2^i,从p夸一次。
  • 答案:计算从u出发走k步,则把k二进制分解,同时进行跳跃:
python 复制代码
u = s = 0
for j in range(k.bit_length()):
	if k>>j&1:
		x = pa[u][j]
		s += f[u][j]	

2. 复杂度分析

  1. 查询query, O(log~2~n)
  2. 初始化,O(nlog~2~n)

3. 常见应用

  1. 步数很大时,快速寻找目标位置:LCA
  2. 快速计算区间值:求和等
  3. 稀疏表ST也用了倍增的思想。

4. 常用优化

  1. m可以初始化为:m = k.bit_length(),代表把k步压二进制最多压成这么多位。注意转移时只要转移range(m-1)。
  2. 如果是lca,计算kth可能越过根时,从高位开始计算可以更快的跳出。
  3. python由于机器缓存的原因,开数组时通常nlg 比lgn强。

二、 模板代码

1. 1483. 树节点的第 K 个祖先(LCA前置模板)

例题: 1483. 树节点的第 K 个祖先

  • 这是离线lca的前置部分,目的是快速求每个节点的第kth个祖先。
  • 求lca的话,把两个节点先调整到同高度,然后从m-1开始向下尝试,大跨步跳即可。复杂度m=log树高
python 复制代码
class TreeAncestor:

    def __init__(self, n: int, parent: List[int]):
        m = n.bit_length()
        self.pa = pa = [[-1]*m for _ in range(n)]
        for u,fa in enumerate(parent[1:],start=1):
            pa[u][0] = fa 
        
        for i in range(m-1):
            for u in range(n):
                if (p:=pa[u][i]) != -1:
                    pa[u][i+1] = pa[p][i]


    def getKthAncestor(self, u: int, k: int) -> int:
        for i in range(k.bit_length()):
            if k>>i&1:
                u = self.pa[u][i]
                if u == -1:
                    break
        return u
    
    def get_lca(self, x: int, y: int) -> int:
        """返回 x 和 y 的最近公共祖先(节点编号从 0 开始)
            思路是先让x,y处于同一层,通过kth跳。
            然后尝试迈大步(2^i步),若迈完发现变成同节点就不迈了,尝试2^(i-1)步。
            最后答案pa[x][0],即x、y一定在lca的直接儿子上,"""
        if self.depth[x] > self.depth[y]:
            x, y = y, x
        # 使 y 和 x 在同一深度
        y = self.get_kth_ancestor(y, self.depth[y] - self.depth[x])
        if y == x:
            return x
        for i in range(len(self.pa[x]) - 1, -1, -1):
            px, py = self.pa[x][i], self.pa[y][i]
            if px != py:
                x, y = px, py  # 同时上跳 2**i 步
        return self.pa[x][0]

2. 在有限状态中转移(957. N 天后的牢房)

链接: 957. N 天后的牢房

  • 由于只有8牢房,那么状态最多256个,必然可以很快进入循环节,所以这题其实正解是用vis模拟找循环节。
  • 但依然由于步数很大,可以考虑用倍增。
  • 提前预处理出来每个状态转移的下一个状态是谁,然后开始倍增。
python 复制代码
class Solution:
    def prisonAfterNDays(self, cells: List[int], n: int) -> List[int]:
        s = int(''.join(map(str,cells)),2)
        m = n.bit_length()
        
        f = [[-1]*m for _ in range(1<<8)]
        for i in range(1<<8):
            p = 0
            for j in range(1,7):
                if (i>>(j-1)&1) == (i>>(j+1)&1):
                    p |= 1<<j 
            f[i][0] = p 
        for i in range(m-1):
            for j in range(1<<8):
                p = f[j][i]
                f[j][i+1] = f[p][i]

        for j in range(m):
            if n>>j&1:
                s = f[s][j]
        
        ans = bin(s)[2:]        
        ans = '0'*(8-len(ans))+ans        
        return [int(c) for c in ans]

3. 需要计算区间贡献(2836. 在传球游戏中最大化函数值)

链接: 2836. 在传球游戏中最大化函数值

  • 周赛T4,比赛中卡住了,后来学习了一下倍增。不亏。
python 复制代码
class Solution:
    def getMaxFunctionValue(self, receiver: List[int], k: int) -> int:
        n = len(receiver)
        m = k.bit_length()
        f = [receiver] + [[0]*n for _ in range(m+1)]
        pa = [receiver] + [[0]*n for _ in range(m+1)]
      
        for i in range(m-1):
            for j in range(n):
                p = pa[i][j]
                pa[i+1][j] = pa[i][p]
                f[i+1][j] = f[i][p] + f[i][j]
        
        ans = 0
        for i,v in enumerate(receiver):
            s = i
            for j in range(k.bit_length()):
                if k >> j&1:
                    s += f[j][i]
                    i = pa[j][i]
            ans = max(ans,s)
        return ans

三、其他

  1. 倍增的lca比较好写,但是依然有一定码量,且只能离线。

四、更多例题

五、参考链接

相关推荐
梧桐树042931 分钟前
python常用内建模块:collections
python
Dream_Snowar39 分钟前
速通Python 第三节
开发语言·python
XH华1 小时前
初识C语言之二维数组(下)
c语言·算法
南宫生1 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
不想当程序猿_2 小时前
【蓝桥杯每日一题】求和——前缀和
算法·前缀和·蓝桥杯
落魄君子2 小时前
GA-BP分类-遗传算法(Genetic Algorithm)和反向传播算法(Backpropagation)
算法·分类·数据挖掘
菜鸡中的奋斗鸡→挣扎鸡2 小时前
滑动窗口 + 算法复习
数据结构·算法
蓝天星空2 小时前
Python调用open ai接口
人工智能·python
Lenyiin2 小时前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin
jasmine s2 小时前
Pandas
开发语言·python