快速幂第二章(C++循环迭代版本)
既然是第二章,那当然有第一章啦!详见快速幂第一章(C++递归版本)。
如果您想要更加透彻地学习快速幂的知识,请务必将这篇文章细细地,一字不落地看完。
前言
当题目要我们求类如 a b = ? a^b=? ab=?的题目时,最直接,最暴力的写法如下:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
int main() {
LL a,b,res=1;
cin>>a>>b;
while(b--) {
res*=a;
}
cout<<res;
}
算法简单明了,循环 b b b次,每次答案 × a ×a ×a。而这类代码,往往是TLE。
TLE很好理解。这段代码的时间复杂度是 O ( b ) O(b) O(b)。当b的数据范围很大时自然会超时。
(时间复杂度如何计算可以参考编者的另一篇博客大 O O O时间复杂度详解)
关于幂的知识
- a b = a × a × . . . × a × a a^b=a×a×...×a×a ab=a×a×...×a×a( b b b个 a a a相乘)
-
a
x
×
a
y
a^x×a^y
ax×ay
= a × a × . . . × a \red a\red ×\red a\red×\red.\red.\red.\red×\red a a×a×...×a ( x (x (x个 a ) a) a) × × × a × a × . . . × a × a \blue a\blue ×\blue a\blue ×\blue .\blue .\blue .\blue ×\blue a\blue ×\blue a a×a×...×a×a ( y (y (y个 a ) a) a)
= ( x + y ) (x+y) (x+y)个 a a a相乘的积
= a x + y a^{x+y} ax+y
例如 2 3 × 2 5 = 2 8 2^3×2^5=2^8 23×25=28
进制转换
对于 10 10 10进制转成 x x x进制,我们可以采用除以 x x x取余法:
这是一种常见的手工计算方法,适用于整数。具体步骤如下:
将十进制数不断除以
x
x
x,直到商为
0
0
0为止,取得的余数从下往上依次排列,即为
x
x
x进制数的结果。
例如:将十进制数
11
11
11转换为二进制:
第一步:
11
÷
2
=
5...1
11÷2=5...1
11÷2=5...1
第二步:
5
÷
2
=
2...1
5÷2=2...1
5÷2=2...1
第三步:
2
÷
2
=
1...0
2÷2=1...0
2÷2=1...0
第四步:
1
÷
2
=
0...1
1÷2=0...1
1÷2=0...1
将得到的余数从上往下排列,得到的结果为
1101
1101
1101
1101 1101 1101是 11 11 11的二进制形式的逆序数(倒过来)。
将 1101 1101 1101反过来, 1011 1011 1011即为所求十进制数 11 11 11的二进制形式。
正文
为了直观简洁,这里用字母 D \scriptsize D D和 B \scriptsize B B表示十进制标识和二进制表标识
比如下面的 5 ( D ) 5\scriptsize(D) 5(D)表示十进制数 5 5 5, 101 ( B ) 101\scriptsize(B) 101(B)表示二进制数 101 101 101。
5
(
D
)
5\scriptsize(D)
5(D)
=
101
=101
=101
(
B
)
\scriptsize(B)
(B)
=
100
(
B
)
=100\scriptsize(B)
=100(B)
+
1
+1
+1
(
B
)
\scriptsize(B)
(B)
=
4
(
D
)
=4\scriptsize(D)
=4(D)
+
1
+1
+1
(
D
)
\scriptsize(D)
(D)
很容易理解吧?
用这种方式,我们可以将任一个数字拆分成若干个 2 2 2的幂次方数相加的形式。
如 11 = 8 + 2 + 1 , 21 = 16 + 4 + 1 , 15 = 8 + 4 + 2 + 1 11=8+2+1,21=16+4+1,15=8+4+2+1 11=8+2+1,21=16+4+1,15=8+4+2+1
根据上面的内容,可以这样将
11
11
11拆分成
8
+
2
+
1
8+2+1
8+2+1的和。
再根据板块二幂的知识可以得到
2
11
=
2
8
×
2
2
×
2
1
2^{11}=2^8×2^2×2^1
211=28×22×21。
现在我们的问题就变成了怎样求 2 8 × 2 2 × 2 1 2^8×2^2×2^1 28×22×21。
我们看一下 11 11 11的二进制形式 1011 1011 1011。
好像发现了什么, 2 11 = 2 8 × 2 2 × 2 1 2^{11}=2^8×2^2×2^1 211=28×22×21,反过来写成另外一种形式就是:
\space\space\space\space\space\space\space\space 2 11 = 1 × 2 1 2^{11}=\blue1×\red2^{\red1} 211=1×21 \space\space × × × \space\space 1 × 2 2 \blue1×\red2 ^{\red2} 1×22 \space\space \space\space × × × \space\space 0 × 2 4 \blue0×\red2^{\red4} 0×24 \space\space \space\space × × × \space\space 1 × 2 8 \blue1×\red2^{\red8} 1×28
注: \space\space 0 × 2 4 0×2^4 0×24 \space\space 不是真正的让答案 × 0 ×0 ×0,而仅仅表示答案在运算这一步时不变。
我们先观察蓝色部分,发现这些数分别是 1 0 1 1 1\space 0\space1\space1 1 0 1 1。 11 ( D ) 11\scriptsize(D) 11(D)= 1101 ( B ) 1101\scriptsize(B) 1101(B)。 1101 1101 1101倒过来是不是与上面算式中的 1011 1011 1011恰好吻合?也就是说,蓝色部分就是 11 ( D ) 11\scriptsize(D) 11(D)的 2 2 2进制的逆序数(反过来)。
我们怎么才能得到一个数 2 2 2进制的逆序数(反过来)形式呢?很简单,只需要不断保存 m o d 2 mod\space2 mod 2的结果,然后不断除以 2 2 2。前面转换二进制的部分提到过。
如
11
11
11,
11
%
2
=
1
,
11
/
2
=
5
;
5
%
2
=
1
,
5
/
2
=
2
;
2
%
2
=
0
,
2
/
2
=
1
;
1
%
2
=
1
,
1
/
2
=
0
11\%2=\red1,11/2=5;5\%2=\red1,5/2=2;2\%2=\red0,2/2=1;1\%2=\red1,1/2=0
11%2=1,11/2=5;5%2=1,5/2=2;2%2=0,2/2=1;1%2=1,1/2=0
红色部分
1
1
0
1
\red1\red1\red0\red1
1101即为所求的
11
11
11的
2
2
2进制形式逆序数。
我们再看每一个部分的后一个数,很明显,它们都是 2 2 2的幂次方,且之间互相存在着关联。 2 8 = 2 4 × 2 4 , 2 4 = 2 2 × 2 2 , 2 2 = 2 1 × 2 1 2^8=2^4×2^4,2^4=2^2×2^2,2^2=2^1×2^1 28=24×24,24=22×22,22=21×21,即每一项都是前一项的平方。
代码:
#include <bits/stdc++.h>
#define LL long long
using namespace std;
int main() {
LL a,b,ans=1,res;
cin>>a>>b;
res=a;
while(b) {
int k=b&1;
if(k) ans*=res;
res=res*res;
b>>=1;
}
cout<<ans;
}
再把公式放一下:
\space\space\space\space\space\space\space\space
11
(
D
)
11\scriptsize(D)
11(D)
=
1011
(
B
)
=1011\scriptsize(B)
=1011(B)
,
,\space
, 逆序为
1101
1101
1101
\space\space\space\space\space\space\space\space
2
11
(
D
)
=
1
×
2
1
2^{11\tiny(D)}=\blue1×\red2^{\red1}
211(D)=1×21
\space\space
×
×
×
\space\space
1
×
2
2
\blue1×\red2 ^{\red2}
1×22
\space\space
\space\space
×
×
×
\space\space
0
×
2
4
\blue0×\red2^{\red4}
0×24
\space\space
\space\space
×
×
×
\space\space
1
×
2
8
\blue1×\red2^{\red8}
1×28
\space\space\space\space\space\space\space\space
3
11
(
D
)
=
1
×
3
1
3^{11\tiny(D)}=\blue1×\red3^{\red1}
311(D)=1×31
\space\space
×
×
×
\space\space
1
×
3
2
\blue1×\red3 ^{\red2}
1×32
\space\space
\space\space
×
×
×
\space\space
0
×
3
4
\blue0×\red3^{\red4}
0×34
\space\space
\space\space
×
×
×
\space\space
1
×
3
8
\blue1×\red3^{\red8}
1×38
\space\space\space\space\space\space\space\space
注:
\space\space
0
×
2
4
\blue0×\red2^{\red4}
0×24
\space\space
不是真正的让答案
×
0
×0
×0,而仅仅表示答案在运算这一步时不变。
代码中的 r e s res res模拟红色部分, a n s ans ans记录答案。
代码第 8 8 8行 i f ( b & 1 ) if(b\&1) if(b&1)我们解释一下, & \& &是按位与。
& \& &的性质: 1 & 1 = 1 , 1 & 0 = 0 , 0 & 1 = 0 , 0 & 0 = 0 1 \& 1 = 1,1\& 0 = 0,0 \& 1 = 0,0 \& 0 = 0 1&1=1,1&0=0,0&1=0,0&0=0
11 & 1 11\&1 11&1的运算过程:
1011
(
B
)
1011\scriptsize(B)
1011(B)
&
1
(
B
)
\&\space\space\space1\scriptsize(B)
& 1(B)
−
-
−
−
-
−
−
-
−
−
-
−
0001
(
B
)
0001\scriptsize(B)
0001(B)
当一个数 & 1 = 1 \&1=1 &1=1时,它就是一个奇数,否则是偶数。 & 1 \&1 &1本质上是通过判断它的二进制数的最后一位是否为 1 1 1来判断它的奇偶性。(其实 & 1 \&1 &1等价于 % 2 \%2 %2, & 1 \&1 &1比 % 2 \%2 %2更快)
第 11 11 11行, b > > = 1 b>>=1 b>>=1也可以写成 b = b > > 1 b=b>>1 b=b>>1, > > >> >>是右移,表示将二进制数的最右边一位舍去。如 101 ( B ) 101\scriptsize(B) 101(B) > > 1 >>1 >>1后是 10 ( B ) 10\scriptsize(B) 10(B)。(其实 > > 1 >>1 >>1等价于 ÷ 2 ÷2 ÷2, > > 1 >>1 >>1比 ÷ 2 ÷2 ÷2更快)
我们不难发现,每次循环第 8 8 8行的 & 1 ( % 2 ) \&1\space(\%2) &1 (%2) 和第 11 11 11行 > > 1 ( ÷ 2 ) >>1\space(÷2) >>1 (÷2) 结合在一起算出的 k k k的其实就是前面提到的 2 2 2进制形式逆序数,为公式的中的蓝色部分。
r e s res res负责模拟红色部分,初始值是 a a a,每次循环它自乘一次,没什么可以多说的。
这样,红色部分和蓝色部分我们都得出来了。
现在,当 b ≠ 0 b≠0 b=0时,在每次循环中,我们只需要在蓝色部分有值的时候将 a n s ans ans改变, × × ×红色部分 r e s res res就行了。
代码能看懂了吗?
我们将第 8 , 9 8,9 8,9行合并一下,就可以得到完整代码:
完整代码:
#include <bits/stdc++.h>
#define LL long long
using namespace std;
int main() {
LL a,b,ans=1,res;
cin>>a>>b;
res=a;
while(b) {
if(b&1) ans*=res;
res*=res;
b>>=1;
}
cout<<ans;
}
模拟
以上这段代码思路正确,没有问题。
让我们根据程序一步步地模拟 2 25 2^{25} 225的计算:
\space\space\space
第
1
1
1步,
b
b
b为
25
25
25
,
b
!
=
0
,b!=0
,b!=0,进入循环:
\space \space\space \space\space \space\space \space
因为
25
&
1
=
1
,
25\&1=1,
25&1=1, 所以执行
a
n
s
=
a
n
s
×
r
e
s
=
1
∗
2
=
2
,
ans=ans×res=1*2=2,
ans=ans×res=1∗2=2,
\space \space\space \space\space \space\space \space
r
e
s
=
r
e
s
×
r
e
s
=
2
∗
2
=
4
,
b
=
b
>
>
1
=
25
/
2
=
12
res=res×res=2*2=4,b=b>>1=25/2=12
res=res×res=2∗2=4,b=b>>1=25/2=12
\space\space\space
第
2
2
2步,
b
b
b为
12
12
12
,
b
!
=
0
,b!=0
,b!=0,进入循环:
\space \space\space \space\space \space\space \space
因为
12
&
1
=
0
,
12\&1=0,
12&1=0, 所以
a
n
s
ans
ans无变化,
\space \space\space \space\space \space\space \space
r
e
s
=
r
e
s
×
r
e
s
=
4
∗
4
=
16
,
b
=
b
>
>
1
=
12
/
2
=
6
res=res×res=4*4=16,b=b>>1=12/2=6
res=res×res=4∗4=16,b=b>>1=12/2=6
\space\space\space
第
3
3
3步,
b
b
b为
6
6
6
,
b
!
=
0
,b!=0
,b!=0,进入循环:
\space \space\space \space\space \space\space \space
因为
6
&
1
=
0
,
6\&1=0,
6&1=0, 所以
a
n
s
ans
ans无变化,
\space \space\space \space\space \space\space \space
r
e
s
=
r
e
s
×
r
e
s
=
16
∗
16
=
256
,
b
=
b
>
>
1
=
6
/
2
=
3
res=res×res=16*16=256,b=b>>1=6/2=3
res=res×res=16∗16=256,b=b>>1=6/2=3
\space\space\space
第
4
4
4步,
b
b
b为
3
3
3
,
b
!
=
0
,b!=0
,b!=0,进入循环:
\space \space\space \space\space \space\space \space
因为
3
&
1
=
1
,
3\&1=1,
3&1=1, 所以执行
a
n
s
=
a
n
s
×
r
e
s
=
2
∗
256
=
512
,
ans=ans×res=2*256=512,
ans=ans×res=2∗256=512,
\space \space\space \space\space \space\space \space
r
e
s
=
r
e
s
×
r
e
s
=
256
∗
256
=
65536
,
b
=
b
>
>
1
=
3
/
2
=
1
res=res×res=256*256=65536,b=b>>1=3/2=1
res=res×res=256∗256=65536,b=b>>1=3/2=1
\space\space\space
第
5
5
5步,
b
b
b为
1
1
1
,
b
!
=
0
,b!=0
,b!=0,进入循环:
\space \space\space \space\space \space\space \space
因为
1
&
1
=
1
,
1\&1=1,
1&1=1, 所以执行
a
n
s
=
a
n
s
×
r
e
s
=
512
∗
65536
=
33554432
,
ans=ans×res=512*65536=33554432,
ans=ans×res=512∗65536=33554432,
\space \space\space \space\space \space\space \space
r
e
s
=
r
e
s
×
r
e
s
=
65536
∗
65536
=
4294967296
,
b
=
b
>
>
1
=
1
/
2
=
0
res=res×res=65536*65536=4294967296,b=b>>1=1/2=0
res=res×res=65536∗65536=4294967296,b=b>>1=1/2=0
\space\space\space 第 6 6 6步, b b b为 0 0 0 , b = = 0 ,b==0 ,b==0,结束循环,输出 a n s ans ans为 33554432 33554432 33554432
整个程序结束。我们用计算器验算也可以得到 2 25 = 33554432 2^{25}=33554432 225=33554432
小结
在计算 a b a^{b} ab时,即使 a a a是一个很小的数,但当 b b b的数据范围较大时, a b a^b ab也极有可能超出 l o n g l o n g long\space long long long的范围,所以我们要注意题目的说法,注意有没有让你对答案进行取模操作。
您听懂了吗?
写作不易,不喜勿喷,留个赞吧!