报刊背景
吃饭的时候,夫人问了一个快速幂的问题。想着干脆写一个博客来给夫人解释。后来又想到,夫人以后还会问问题,干脆搞一个小报刊,每次问问题都记录下来,写一篇博客来给夫人解释相关算法。因此,《科科公主小报刊》正式创刊!
1. 快速幂
1.1 问题引入
现在在举办科科王国一年一度的最强恋爱脑,比赛规则谁最先回答出下面问题,谁就是最强恋爱脑。
请口算出 2 20 2^{20} 220 是多少?
现在参赛选手 小科
,开始一个一个乘
2
2
2,乘了
20
20
20 次,终于得出结果。
不过参赛选手 飞飞
,他庆祝过程序员节,知道
1024
1024
1024 是
2
10
2^{10}
210,而
2
20
2^{20}
220 是两个
2
10
2^{10}
210 相乘,所以 飞飞
直接用
1024
∗
1024
1024 * 1024
1024∗1024,运算了一次求出结果。成功卫冕 最强恋爱脑!
1.2 问题分析
比赛结束之后,我们的 最强恋爱脑 接受了本台记者采访,让我们一起看一看他的成功之道。
科科小记者
:亲爱的 恋爱脑脑王 先生,请问您成功的秘诀是什么?
脑王
:
快速求出 n n n 阶幂函数的诀窍在于,事先知道它的 n 2 \frac{n}{2} 2n 阶幂函数是多少!
如果知道了就可以直接相乘 1 1 1 次求出。就不用再从 n 2 \frac{n}{2} 2n 运算 n 2 \frac{n}{2} 2n 次到 n n n 阶。直接减少了 n 2 − 1 \frac{n}{2}-1 2n−1次运算。
科科小记者
:可是我们又怎知道 n 2 \frac{n}{2} 2n 阶的幂函数呢?
脑王
:
那我们就可以故技重施,还用刚才的策略:
如果知道 n 4 \frac{n}{4} 4n 的话,就可以快速求出 n 2 \frac{n}{2} 2n 的值。
求 n 4 \frac{n}{4} 4n 的话,就可以先求 n 8 \frac{n}{8} 8n 的值。
… \dots …
循环往复,阶数逐渐递减,直到此时求得的 1 1 1 阶的值,我们知道 2 1 = 2 2^1 = 2 21=2。
然后我们就在从刚才的寻求的路径回去,知道 1 1 1 阶快速求出 2 2 2阶,知道 2 2 2 阶快速求出 4 4 4阶,进而 8 8 8 阶, 16 16 16 阶,直到求出 n n n 阶。
科科小记者
:哇,我知道你刚才说的其实就是 递归 的过程。可是我还有一个问题,上面的都是偶数,才可以一直除于 2 2 2。可是如果求的是 2 5 2^5 25,阶数是一个奇数,总不能求 2.5 2.5 2.5 阶的值吧?
脑王
:
你真是一个善于思考而且博学的小姑娘啊。知道递归,而且还能举一反三,想到奇数不适用的情况。
我们不妨设 A n s = 2 n Ans = 2^n Ans=2n,如果此时 n n n 是奇数的话,那我们可以转化为 A n s = 2 × 2 n − 1 Ans = 2 \times 2^{n-1} Ans=2×2n−1
此时 n − 1 n-1 n−1 就是偶数了,就可以继续上面的过程。每次我们遇见奇数,都可以这样做。
科科小记者
:不愧是脑王,我懂了脑王哥哥。( ˘ ³˘)♥
1.3 递归写法
通过上述采访,我们其实已经得出了求解思路,接下来我们就可以着手实现代码。
- 通常快速幂的使用场景都是求 a b a^b ab 对 m o d mod mod 取模。
- 通常
m
o
d
mod
mod 为
1000000007
1000000007
1000000007,所以为了防止爆范围,一般使用
long long
const int mod = 1e9+7;
long long qpow(long long base, long long x){
// base是底数,x是阶数
if(!x) return 1;//x为 0 阶的时候,返回 1
//x 为奇数的时候,x-1就是偶数
if(x&1) return base * qpow(base, x-1) % mod;
//偶数的时候,就可以用 x/2 阶两个相乘
return qpow(base, x/2) * qpow(base, x/2) % mod;
}
1.4 循环写法
除了递归写法之外,还有循环写法。而且在比赛中,循环写法是更常见的。
循环写法的思路与上述基本一脉相承,只不过实现的角度刚好相反,递归是从大到小,再从小到大;循环是从小到大阶数来求。
思考对于求 A n s = a b Ans = a ^ b Ans=ab 时:
- 如果 b b b 是偶数的时候,那么 A n s = a b = ( a 2 ) b 2 Ans = a ^ b = (a^2)^{\frac{b}{2}} Ans=ab=(a2)2b
- 如果 b b b 是奇数的时候,那么 A n s = a × ( a 2 ) b − 1 2 Ans = a \times (a^2)^{\frac{b-1}{2}} Ans=a×(a2)2b−1
那么我们就可以
- 设变量
b
a
s
e
base
base 表示底数。最开始求
a
b
a^b
ab,
b
a
s
e
=
a
base = a
base=a。往后每次循环,base变为
b
a
s
e
2
base^2
base2
即 b a s e = b a s e ∗ b a s e base = base * base base=base∗base - 变量
b
b
b 表示阶数。每次 base 变为平方后,阶数要除于
2
2
2。
即 b / = 2 b /= 2 b/=2 - 如果遇到
b
b
b 为奇数的时候,把一个
b
a
s
e
base
base 乘给
A
n
s
Ans
Ans
即 A n s = A n s ∗ b a s e Ans = Ans * base Ans=Ans∗base
const int mod = 1e9+7; //模数
long long qpow(long long base, long long x){// base为基数,x为阶数
long long ans = 1;// 最终答案
while(x){
//如果x为奇数,那么 ans = base * base ^(x-1)
if(x&1) ans = ans * base % mod;//为奇数,就先把一个base乘给ans
base = base * base % mod; // 此时求出 base' = base^2
x >>= 1;
//当 Ans = base ^ x , base' = base ^ 2时,则 Ans = base' ^ (x/2)
//所以 x 需要除于2
}
return ans;
}
2. 快速乘
一年一度的最强恋爱脑 再次来开帷幕,比赛规则仍是谁最先回答出问题,即可夺得最强恋爱脑的头衔。
请求出 52013141314 × 52113141314 52013141314 \times 52113141314 52013141314×52113141314 对 131413141314 131413141314 131413141314 取模之后的值
现在有 电灯泡
,军师
,飞飞
,科科
四位选手来答题。
电灯泡
: 电灯泡直接让两个数相乘,然后取模。但是可惜,long long 的最大范围只有
2
63
−
1
2^{63}-1
263−1,容不下两个十一位数的相乘。
军师
:军师敏锐地发现了这一点,然后他竟然进行了
52013141314
52013141314
52013141314 次
52113141314
52113141314
52113141314 相加,每次相加之后取模。可惜用了
O
(
N
)
O(N)
O(N) 的时间,太慢了。
飞飞
:上届脑王竟然再次使出上届的快速幂的招数!OMG! 用了
O
(
log
N
)
O(\log N)
O(logN) 的时间解决了!怕是无人能出其右!恐怕他要成功卫冕最强恋爱脑了。
科科
:等等!还有高手。这是什么,已经结束了吗?太快了,根本没看见!是
O
(
1
)
O(1)
O(1)!竟然瞬间求出来了!!!
我宣布,本次大赛 最强恋爱脑 就是 科科
!
2.1 龟速乘
让我们来看一下 飞飞
的解法。思路和快速幂的思路一致!
欲求
52013141314
52013141314
52013141314 次
52113141314
52113141314
52113141314相加,如果我们知道
2
×
52113141314
2 \times 52113141314
2×52113141314,就可以只进行
52013141314
2
\frac{52013141314}{2}
252013141314 次相加了。依次类推。
代码使用将快速幂的乘法换成加法即可!
const int mod = 131413141314; //模数
long long qmul(long long base, long long x){// base,x为两个乘数
long long ans = 0;// 最终答案初始化为0
while(x){
//如果x为奇数,那么 ans = base * base ^(x-1)
if(x&1) ans = (ans + base) % mod;//为奇数,就先把一个base乘给ans
base = (base << 1) % mod; // 此时求出 base' = base*2
x >>= 1;
//当 Ans = base * x , base' = base * 2时,则 Ans = base' * (x/2),所以 x 需要除于2
}
return ans;
}
虽然和 快速幂 相似,但是因为它的落败,这种方法并不能称作 快速乘,只能叫做 龟速乘了!
2.2 快速乘
让我们来采访一下本届的 最强恋爱脑 科科选手,来看一看真正的快速乘。
科科
:上一届痛失恋爱脑,今年我加倍努力,看了很多文章,终于成为恋爱中的最强恋爱脑。
飞飞小记者
:请问,如果实现的 O ( 1 ) O(1) O(1) 复杂度的求解呢?
科科
:这还是得益于一篇2009年国家集训队的论文 —— 骆可强:《论程序底层优化的一些方法与技巧》中 优化mul_mod函数 中提到的技巧。
这种写法需要对于位运算和进制十分熟悉,才能够理解。
通过文章中的内容,我们可以得到以下代码:
typedef long long ll;
typedef long double ld;
ll ksc(ll x,ll y,ll mod){
ll z=(ld)x*y/mod;
ll res=x*y-z*mod;
return (res+mod)%mod;
}
第一步,可以将 z z z 看作 z = ⌊ x × y m o d ⌋ z = \lfloor \frac{x \times y}{mod} \rfloor z=⌊modx×y⌋
第二步,虽然
x
×
y
x \times y
x×y 和
z
×
m
o
d
z \times mod
z×mod都会溢出 unsigned long long
的范围,但是我们需要求的是 long long
内的值,所以两者溢出的不会影响
将 x × y − ⌊ x × y m o d ⌋ × m o d x \times y - \lfloor \frac{x \times y}{mod} \rfloor \times mod x×y−⌊modx×y⌋×mod 就看做 x × y % m o d x \times y \% mod x×y%mod 的值
第三步,可能 r e s res res 为负数,所以 ( r e s + m o d ) % m o d (res + mod) \% mod (res+mod)%mod 来避免负数。
写在最后
一直说要给科科写情书,但一直没写。这个就算
第一封情书
,送给我的科科公主。
我之前向她描绘说,要写一些博客,然后每当有朋友看到了这篇博客,就麻烦朋友花费一点时间,给我们留言祝99
。每天每时每刻,都可能回多一些留言,这就是对我们的祝福!
如果各位同学觉得本篇博客对您有帮助的话,请麻烦您花一分钟时间,请在评论区留言 qk & hhf 99
。您的每一段留言,在我们看来都珍贵无比。