快速幂算法--求 a^b % p 的快速方法
先想暴力怎么求解
可以循环b次,每次从而求出a^b % p,时间复杂度为O(b),而这里的b是很大的,达到了2 * 10 ^ 9数量级,所以这么做会TLE
1 #include <iostream> 2 using namespace std; 3 int main() { 4 int a, b, p; 5 long long res = 1; 6 cin >> a >> b >> p; 7 while(b--) 8 res = res * a % p; 9 cout << res << endl; 10 }
如何优化?
我们可以用二分的思想来优化:
1) 如果b是偶数,我们可以将b / 2,求出a ^ (b / 2),然后再求平方;
2) 如果b是奇数,我们可以先将b - 1,b - 1即为偶数,记b' = b - 1,求出a ^ (b' / 2),求平方后再乘以a,仍然可以得到正确结果
3) 递归出口为a ^ 0 = 1
以上就是递归求快速幂的思路,代码如下:
1 //递归快速幂 2 int qmi(int a, int n) 3 { 4 if (n == 0) 5 return 1; 6 else if (n % 2 == 1) 7 return qmi(a, n - 1) * a; 8 else 9 { 10 int temp = qmi(a, n / 2); 11 return temp * temp; 12 } 13 }
这里的temp是需要的,如果不加temp的话,直接return qmi(a, n / 2) * qmi(a, n / 2),那么会计算两次an/2,这样算法就退化成了O(n)。
在实际问题中,题目常常会要求对一个大素数取模,这是因为计算结果可能会非常巨大,但是在这里考察高精度又没有必要。这时我们的快速幂也应当进行取模,此时应当注意,原则是步步取模,如果MOD较大,还应当开long long。
1 //递归快速幂(对大素数取模) 2 #define MOD 1000000007 3 typedef long long ll; 4 ll qpow(ll a, ll n) 5 { 6 if (n == 0) 7 return 1; 8 else if (n % 2 == 1) 9 return qpow(a, n - 1) * a % MOD; 10 else 11 { 12 ll temp = qpow(a, n / 2) % MOD; 13 return temp * temp % MOD; 14 } 15 }
递归虽然简洁,但会产生额外的空间开销。我们可以把递归改写为循环,来避免对栈空间的大量占用,也就是非递归快速幂。
非递归快速幂
这里用一个例子更形象地说明,比如计算7的10次方;这里可以把10写成二进制,即(1010)2
也就是计算 7(1010)2 可以把它拆成 7(1000)2 * 7(10)2
对于任意的整数,我们都可以把它拆成二进制数的形式
那么,我们就可以每次将底数平方,从而不断地得到我们所需要的乘数因子
上代码:
1 //非递归快速幂 2 int qpow(int a, int n){ 3 int ans = 1; 4 while(n){ 5 if(n&1) //如果n的当前末位为1 6 ans *= a; //ans乘上当前的a 7 a *= a; //a自乘 8 n >>= 1; //n往右移一位 9 } 10 return ans; 11 }
仔细推敲代码过程:
最初ans为1,然后一位一位算:
1010的最后一位是0,所以a^1这一位不要。然后1010变为101,a变为a^2。(a ^ 2)
101的最后一位是1,所以a^2这一位是需要的,乘入ans。101变为10,a再自乘。(a ^ 4)
10的最后一位是0,跳过,右移,自乘。(a ^ 8)
然后1的最后一位是1,ans再乘上a^8。循环结束,返回结果。
>>是右移,表示把二进制数往右移一位,相当于/2;&是按位与,&1可以理解为取出二进制数的最后一位,相当于%2==1。
下面给出Acwing上模板题的代码
1 #include <bits/stdc++.h> 2 using namespace std; 3 #define int long long 4 5 int qmi(int a, int b, int p) { 6 int ans = 1; 7 while(b) { 8 if(b & 1) ans = ans * a % p; 9 a = a * a % p; 10 b = b >> 1; 11 } 12 return ans; 13 } 14 15 signed main() { 16 int T; 17 cin >> T; 18 while(T--) { 19 int a, b, p; 20 cin >> a >> b >> p; 21 int res = qmi(a, b, p); 22 cout << res << '\n'; 23 } 24 return 0; 25 }
参考博客
https://zhuanlan.zhihu.com/p/95902286
上一篇: 求 a 和 b 的最大公约数
下一篇: 关于个人做淘宝客网站
推荐阅读
-
快速幂调制算法的 Java 语言实现细节
-
快速幂:求 A^B 的后三位整数。注:AB 表示 "A 的 B 次幂"。
-
快速幂算法--求 a^b % p 的快速方法
-
5分钟快速了解:王垠面试中的算法点滴与P vs NP问题解读
-
如何快速在Excel中计算A列与B列数据的乘积然后求和? 步骤一:在D2单元格内输入公式 "=SUM(A2:A13*B2:B13)" , 步骤二:按下Enter键,就能得到A、B两列对应数值相乘后再求和的结果。 另外一种简便做法: 方法二:选中D2到D13区域,点击“公式”选项卡,选择“ sumproduct ”函数,然后分别在相应的A、B列范围框内输入 =SUMPRODUCT(A2:A13,B2:B13) ,点击确定即可得到所需结果。
-
搞定C语言算法中的数论基础:模板快速幂的实现方法
-
【Netty】「萌新入门」(七)ByteBuf 的性能优化-堆内存的分配和释放都是由 Java 虚拟机自动管理的,这意味着它们可以快速地被分配和释放,但是也会产生一些开销。 直接内存需要手动分配和释放,因为它由操作系统管理,这使得分配和释放的速度更快,但是也需要更多的系统资源。 另外,直接内存可以映射到本地文件中,这对于需要频繁读写文件的应用程序非常有用。 此外,直接内存还可以避免在使用 NIO 进行网络传输时发生数据拷贝的情况。在使用传统的 I/O 时,数据必须先从文件或网络中读取到堆内存中,然后再从堆内存中复制到直接缓冲区中,最后再通过 SocketChannel 发送到网络中。而使用直接缓冲区时,数据可以直接从文件或网络中读取到直接缓冲区中,并且可以直接从直接缓冲区中发送到网络中,避免了不必要的数据拷贝和内存分配。 通过 ByteBufAllocator.DEFAULT.directBuffer 方法来创建基于直接内存的 ByteBuf: ByteBuf directBuf = ByteBufAllocator.DEFAULT.directBuffer(16); 通过 ByteBufAllocator.DEFAULT.heapBuffer 方法来创建基于堆内存的 ByteBuf: ByteBuf heapBuf = ByteBufAllocator.DEFAULT.heapBuffer(16); 注意: 直接内存是一种特殊的内存分配方式,可以通过在堆外申请内存来避免 JVM 堆内存的限制,从而提高读写性能和降低 GC 压力。但是,直接内存的创建和销毁代价昂贵,因此需要慎重使用。 此外,由于直接内存不受 JVM 垃圾回收的管理,我们需要主动释放这部分内存,否则会造成内存泄漏。通常情况下,可以使用 ByteBuffer.clear 方法来释放直接内存中的数据,或者使用 ByteBuffer.cleaner 方法来手动释放直接内存空间。 测试代码: public static void testCreateByteBuf { ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(16); System.out.println(buf.getClass); ByteBuf heapBuf = ByteBufAllocator.DEFAULT.heapBuffer(16); System.out.println(heapBuf.getClass); ByteBuf directBuf = ByteBufAllocator.DEFAULT.directBuffer(16); System.out.println(directBuf.getClass); } 运行结果: class io.netty.buffer.PooledUnsafeDirectByteBuf class io.netty.buffer.PooledUnsafeHeapByteBuf class io.netty.buffer.PooledUnsafeDirectByteBuf 池化技术 在 Netty 中,池化技术指的是通过对象池来重用已经创建的对象,从而避免了频繁地创建和销毁对象,这种技术可以提高系统的性能和可伸缩性。 通过设置 VM options,来决定池化功能是否开启: -Dio.netty.allocator.type={unpooled|pooled} 在 Netty 4.1 版本以后,非 Android 平台默认启用池化实现,Android 平台启用非池化实现; 这里我们使用非池化功能进行测试,依旧使用的是上面的测试代码 testCreateByteBuf,运行结果如下所示: class io.netty.buffer.UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeDirectByteBuf class io.netty.buffer.UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf class io.netty.buffer.UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeDirectByteBuf 可以看到,ByteBuf 类由 PooledUnsafeDirectByteBuf 变成了 UnpooledUnsafeDirectByteBuf; 在没有池化的情况下,每次使用都需要创建新的 ByteBuf 实例,这个操作会涉及到内存的分配和初始化,如果是直接内存则代价更为昂贵,而且频繁的内存分配也可能导致内存碎片问题,增加 GC 压力。 使用池化技术可以避免频繁内存分配带来的开销,并且重用池中的 ByteBuf 实例,减少了内存占用和内存碎片问题。另外,池化技术还可以采用类似 jemalloc 的内存分配算法,进一步提升分配效率。 在高并发环境下,池化技术的优点更加明显,因为内存的分配和释放都是比较耗时的操作,频繁的内存分配和释放会导致系统性能下降,甚至可能出现内存溢出的风险。使用池化技术可以将内存分配和释放的操作集中到预先分配的池中,从而有效地降低系统的内存开销和风险。 内存释放 当在 Netty 中使用 ByteBuf 来处理数据时,需要特别注意内存回收问题。 Netty 提供了不同类型的 ByteBuf 实现,包括堆内存(JVM 内存)实现 UnpooledHeapByteBuf 和堆外内存(直接内存)实现 UnpooledDirectByteBuf,以及池化技术实现的 PooledByteBuf 及其子类。 UnpooledHeapByteBuf:通过 Java 的垃圾回收机制来自动回收内存; UnpooledDirectByteBuf:由于 JVM 的垃圾回收机制无法管理这些内存,因此需要手动调用 release 方法来释放内存; PooledByteBuf:使用了池化机制,需要更复杂的规则来回收内存; 由于池化技术的特殊性质,释放 PooledByteBuf 对象所使用的内存并不是立即被回收的,而是被放入一个内存池中,待下次分配内存时再次使用。因此,释放 PooledByteBuf 对象的内存可能会延迟到后续的某个时间点。为了避免内存泄漏和占用过多内存,我们需要根据实际情况来设置池化技术的相关参数,以便及时回收内存; Netty 采用了引用计数法来控制 ByteBuf 对象的内存回收,在博文 「源码解析」ByteBuf 的引用计数机制 中将会通过解读源码的形式对 ByteBuf 的引用计数法进行深入理解; 每个 ByteBuf 对象被创建时,都会初始化为1,表示该对象的初始计数为1。 在使用 ByteBuf 对象过程中,如果当前 handler 已经使用完该对象,需要通过调用 release 方法将计数减1,当计数为0时,底层内存会被回收,该对象也就被销毁了。此时即使 ByteBuf 对象还在,其各个方法均无法正常使用。 但是,如果当前 handler 还需要继续使用该对象,可以通过调用 retain 方法将计数加1,这样即使其他 handler 已经调用了 release 方法,该对象的内存仍然不会被回收。这种机制可以有效地避免了内存泄漏和意外访问已经释放的内存的情况。 一般来说,应该尽可能地保证 retain 和 release 方法成对出现,以确保计数正确。
-
计算机视觉中,究竟有哪些好用的目标跟踪算法(下)-快速变形主要因为CF是模板类方法。容易跟丢这个比较好理解,前面分析了相关滤波是模板类方法,如果目标快速变形,那基于HOG的梯度模板肯定就跟不上了,如果快速变色,那基于CN的颜色模板肯定也就跟不上了。这个还和模型更新策略与更新速度有关,固定学习率的线性加权更新,如果学习率太大,部分或短暂遮挡和任何检测不准确,模型就会学习到背景信息,积累到一定程度模型跟着背景私奔了,一去不复返。如果学习率太小,目标已经变形了而模板还是那个模板,就会变得不认识目标。(举个例子,多年不见的同学,你很可能就认不出了,而经常见面的同学,即使变化很大你也认识,因为常见的同学在你大脑里面的模型在持续更新,而多年不见就是很久不更新) 快速运动主要是边界效应(Boundary Effets),而且边界效应产生的错误样本会造成分类器判别力不够强,下面分训练阶段和检测阶段分别讨论。 训练阶段,合成样本降低了判别能力。如果不加余弦窗,那么移位样本是长这样的: 除了那个最原始样本,其他样本都是“合成”的,100*100的图像块,只有1/10000的样本是真实的,这样的样本集根本不能拿来训练。如果加了余弦窗,由于图像边缘像素值都是0,循环移位过程中只要目标保持完整那这个样本就是合理的,只有目标中心接近边缘时,目标跨越边界的那些样本是错误的,这样虽不真实但合理的样本数量增加到了大约2/3(padding= 1),即使这样仍然有1/3(3000/10000)的样本是不合理的,这些样本会降低分类器的判别能力。再者,加余弦窗也不是“免费的”,余弦窗将图像块的边缘区域像素全部变成0,大量过滤掉分类器本来非常需要学习的背景信息,原本训练时判别器能看到的背景信息就非常有限,我们还加了个余弦窗挡住了背景,这样进一步降低了分类器的判别力(是不是上帝在我前遮住了帘。不是上帝,是余弦窗)。 检测阶段,相关滤波对快速运动的目标检测比较乏力。相关滤波训练的图像块和检测的图像块大小必须是一样的,这就是说你训练了一个100*100的滤波器,那你也只能检测100*100的区域,如果打算通过加更大的padding来扩展检测区域,那样除了扩展了复杂度,并不会有什么好处。目标运动可能是目标自身移动,或摄像机移动,按照目标在检测区域的位置分四种情况来看: 如果目标在中心附近,检测准确且成功。 如果目标移动到了边界附近但还没有出边界,加了余弦窗以后,部分目标像素会被过滤掉,这时候就没法保证这里的响应是全局最大的,而且,这时候的检测样本和训练过程中的那些不合理样本很像,所以很可能会失败。 如果目标的一部分已经移出了这个区域,而我们还要加余弦窗,很可能就过滤掉了仅存的目标像素,检测失败。 如果整个目标已经位移出了这个区域,那肯定就检测失败了。 以上就是边界效应(Boundary Effets),推荐两个主流的解决边界效应的方法,但速度比较慢,并不推荐用于实时场合。