倍增算法

白话讲解:转载原地址

【序言】

        我认为吧,所有能够优化复杂度的算法都是神奇的,所有能够化繁琐为形象的文字都是伟大的。一直觉得倍增算法是个很神奇的东西,所以决定写点东西纪念一下它。但是作为一个非常不称职的OIER,我非常讨厌在看别人的算法解析时整版的i,j,k等我看到鼠标就惯性移到右上角的符号语言,所以我想用最形象的方式来纪念它。

 

【一】

        从前,有一只可爱得不得了的小白兔,它想从A地去往遥远的B地。

 

        2B小白兔:

            向右边跳一步,左边跳一步,再向右边跳很多步,再……(对不起,这个太脑残了)

        普通小白兔:

            向右边跳一步,再跳一步,再跳一步……再跳一步,哇,到了!好开心!

        超级小白兔:

            向右边跳一大步,一步跳到B,然后默默回头,鄙视一下那只一步一步跳的小白兔。

 

        我相信作为一个正常人,是不会考虑到2B小白兔的这种做法的,因为它太脑残了。

        同时我也相信,作为一个正常人,也不会考虑到超级小白兔的这种做法的,因为……

            “我擦!你什么时候说可以这样跳了!(愤怒)”

            “我什么时候说不可以了!(卖萌)”

        但是你不得不承认,超级小白兔还是有两把刷子的,因为它真的是太厉害了,厉害得你想都没有想到。

 

【二】

        从前,有一只可爱得不得了的小白兔,它想从A地去往遥远的B、C、D、E、F这几个让它魂牵梦萦的地方。(不要问我从哪里来,我的梦想在远方)

 

 

        2B小白兔:

            (对不起,我的生命有限,我不想再提到它了)

        普通小白兔:

            一步又一步,生命不息,跳跃不止。

        超级小白兔:

            一步到B,再一步到C,再一步到D,再一步到E,再一步到F,完工。

 

        你不用解释,我深知你就想当那只超级小白兔,哼,你肯定是那样跳的。(神马?不是?兄弟你的智商我救不了了……)

        是的,不这样跳的人对不起社会啊,浪费时间就是浪费青春,浪费青春就是犯罪。

        好的,既然你是这样跳的,那你能告诉我你是怎么知道从A只要跳2个格子就会刚好到B的?难道你在空中使用了GPRS全球卫星定位系统?你少来好么!主页君现在都没用过这种东西,你一只白白嫩嫩下酒菜的小兔子还用这个?笑死我吗?哈哈,你一定是出发前就偷偷学普通兔子一步一步跳过一遍,然后拿个本子做小抄,记录好从每个格子跳任意步会到达的地方,然后赶在天亮之前回来,风光的按照踩点计划大步的跳,让我们觉得你很厉害的样子,我没说错吧?不过看在你有这个诚心的份上,还是为你的聪明鼓掌吧。

 

【三】

        从前,有一只可爱得不得了的小白兔,它想从A地去往遥远的1(此处省略很多0)个地方,因为它真的是太没事情做了。

 

 

        普通小白兔:

            从离开家门的那天起,我就没有想过要放弃一步一步地跳往终点。(嗯,加油)

        超级小白兔:

            轻轻松松,绝不多走一步。(哼哼)

 

        你想知道最后的结果吗?呵呵,好像还没有出结果……

        写给普通小白兔的话:

            亲爱的小白兔,我知道你勇毅,你质朴,但是,苦海无涯,回头是岸。

        写给超级小白兔的话:

            我不知道你的小抄本是否还够用,我不知道你摸着黑就出门是为了什么,你不觉得你的行踪早就已经暴露了吗?你以为你很聪明吗?不,你错了,你就一下酒菜,永远都是,因为你不知道倍增算法,这是你失败的根源,再见,我心中永远不会逝去的蠢兔子。

 

---------------------------------------------------------------------------------------------- 卖萌分割线 ----------------------------------------------------------------------------------------------------

 

        普通兔子 = 速度慢,无资源损耗 || 超级兔子 = 速度快,多资源损耗

 

        还记得那只离我们远去的2B兔子吗?对,其实我们早该想到了,越蠢得不可思议的兔子身上竟然有巨大的宝藏,再看看它的名字吧,“2B”!去掉一个“B”!就是“2”!对,你没有听错,就是“2”,你能想到4、8、16、32吗?

        再想想,超级兔子的小抄本不够用,不就是因为它为了应对所有的目的地信息,它记录下了任何一个格子跳任意步会到达的格子,100个格子它要记录大概5000条信息,1000个格子大概要记录500000条信息,10000个格子它大概要记录50000000条信息,至于你晕没晕,我相信它应该晕了。

        可不可以把记录的信息数降到最低呢?当然可以,2B兔子帮你忙,让你用2战胜敌人。

        当你只记录任何一个格子跳1、2、4、8、16……步会到达的格子的时候,你有没有发现信息数突然少了好多好多啊!真的少了好多好多啊!100个格子只要500条左右,1000个格子只要5000条左右,10000个格子只要50000条左右,不比不知道,一比吓一跳啊!

        安心小抄五十年,健康生活一辈子。

        从此,超级小白兔成为了聪明小白兔,它的生活是这样的:

 

        在夜深人静的时候,它偷偷出门做小抄,记录下从每个格子跳1、2、4、8……个格子后会到达的格子,然后在太阳出来后,它在众目睽睽之下,开始了表演。

        从A出发:若跳8个格子(超过B了,放弃)

                  若跳4个格子(超过B了,放弃)

                  若跳2个格子(没超过B,跳吧)

                  若跳1个格子(没超过B,跳吧)

        从B出发:…………

 

        多么轻松的事情,只要一本很薄的小抄就可以了,最关键的是:它绝对不会连着跳两步都是跳相同的格子数,因为如果跳两次2个格子都是可行的话,那么它干嘛不跳4个格子捏?

        我们可是从多到少来判断的啊!!

 

        好的,聪明小白兔白天的事情你已经看懂了,且看它晚上是怎么打小抄的吧。

        从A出发跳1步到1(记录下来)

        从1出发跳1步到2(记录下来)

        …………(跳1步的记录完毕)

 

        从A出发跳2步?就是从A出发跳1步再跳1步的到的地方,翻看小抄,直接誊写从1出发跳1步会到的2这个格子作为A跳2步会到的格子。

        从1出发跳2步?跟刚才一样,直接誊写。

        …………(跳2步的记录完毕)

 

        从A出发跳4步?你还真去跳4步?不,它也就是等于从A出发跳2步到的2号点再跳2步会到的格子,那么我们直接誊写2号格子跳2步会到的格子就可以了。

        ……

        ……

 

        看看聪明小白兔多么聪明!也许还有自认为更聪明的:

            “在记录A跳4步会到的格子的时候,为什么不直接从A跳4步看到了哪里再记录下来呢?跳4步跟跳1步的代价不是一样的么”

            “我这样回答你好了!把你丢在纽约的一个公交车站,问你一条线路的下一个站是什么?你怎么办?当然是自己亲自走到下一个站就知道了!那如果问你接下来的第4个站是什么?难道你可以直接走到第4个站而不用途径其它的站点了吗?这不现实,你还是要一个一个站的走,因为关键在于你只能知道你目前所在站点的下一个站是什么,想知道下下个站,除非你已经到了下个站,兔子跳格子也跟这类似,虽然聪明小白兔神通广大,但是不至于伟大到可以提前预知跳几步会到哪里啊!!”

 

        从此,聪明小白兔避免了成为人类的下酒菜,而被一群OIER们像神一样的供奉了起来,不要问它为什么,因为它也不知道,貌似只是某人卖了个小萌,事情就变成这样了。

 

                                                                                                                                                                                                           

### Python 中倍增算法的实现与应用 #### 什么是倍增算法倍增算法是一种基于分治思想的优化技术,其核心在于通过逐步扩展或缩小处理范围的一半来提升效率。这种算法可以将原本的时间复杂度 \( O(n) \) 降低至 \( O(\log n) \)[^4]。 #### 倍增算法的应用场景 倍增算法广泛应用于多种领域,例如: - **快速幂运算**:利用倍增思想计算指数幂。 - **最近公共祖先 (LCA)**:在树结构中找到两个节点的最近公共祖先。 - **二分查找**:在有序序列中定位目标值的位置。 - **字符串匹配**:如 AC 自动机中的状态转移优化[^5]。 --- #### 快速幂运算的实现 以下是使用倍增思想实现快速幂的一个例子: ```python def fast_power(base, exponent, mod=None): result = 1 while exponent > 0: if exponent % 2 == 1: # 如果当前指数为奇数,则乘上 base result = (result * base) % mod if mod else result * base base = (base * base) % mod if mod else base * base # 平方操作 exponent //= 2 # 指数减半 return result ``` 上述代码实现了模意义下的快速幂运算,时间复杂度为 \( O(\log e) \),其中 \( e \) 是指数[^1]。 --- #### 最近公共祖先 (LCA) 的实现 倍增法常被用来解决 LCA 问题。具体思路是预处理每个节点向上跳跃 \( 2^k \) 步后的父节点,并结合深度信息完成查询。 ```python from math import log2 class LCA: def __init__(self, tree, root=0): self.tree = tree self.n = len(tree) self.log_n = int(log2(self.n)) + 1 self.depth = [-1] * self.n self.up = [[-1] * self.log_n for _ in range(self.n)] self.dfs(root, -1) def dfs(self, node, parent): self.depth[node] = self.depth[parent] + 1 self.up[node][0] = parent for k in range(1, self.log_n): if self.up[node][k-1] != -1: self.up[node][k] = self.up[self.up[node][k-1]][k-1] for child in self.tree[node]: if child != parent: self.dfs(child, node) def get_lca(self, u, v): if self.depth[u] < self.depth[v]: u, v = v, u # 将 u 和 v 调整到同一深度 for k in range(self.log_n - 1, -1, -1): if self.up[u][k] != -1 and self.depth[self.up[u][k]] >= self.depth[v]: u = self.up[u][k] if u == v: return u # 继续调整直到两者相遇 for k in range(self.log_n - 1, -1, -1): if self.up[u][k] != -1 and self.up[u][k] != self.up[v][k]: u = self.up[u][k] v = self.up[v][k] return self.up[u][0] ``` 此代码展示了如何构建一个支持倍增的 LCA 查询工具类[^3]。 --- #### 字符串匹配中的倍增思想 在 AC 自动机的状态转移过程中,可以通过倍增的方式预先计算失败指针,从而加快模式匹配的速度。这种方法的核心是在建图阶段记录每步可能的最大跳转距离。 --- #### 使用 NumPy 进行矩阵优化 尽管 Python 提供了强大的第三方库(如 NumPy),但在竞赛环境中并不总是可用。因此,在设计解决方案时应优先考虑纯 Python 实现[^2]。 ---
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值