【科科公主小报刊】快速幂&快速乘


报刊背景

吃饭的时候,夫人问了一个快速幂的问题。想着干脆写一个博客来给夫人解释。后来又想到,夫人以后还会问问题,干脆搞一个小报刊,每次问问题都记录下来,写一篇博客来给夫人解释相关算法。因此,《科科公主小报刊》正式创刊!

漂亮温柔美丽大方的科科小公主


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 10241024,运算了一次求出结果。成功卫冕 最强恋爱脑

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 2n1次运算。

科科小记者:可是我们又怎知道 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×2n1
此时 n − 1 n-1 n1 就是偶数了,就可以继续上面的过程。每次我们遇见奇数,都可以这样做。

科科小记者:不愧是脑王,我懂了脑王哥哥。( ˘ ³˘)♥

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)2b1

那么我们就可以

  • 设变量 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=basebase
  • 变量 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=Ansbase
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 2631,容不下两个十一位数的相乘。

军师:军师敏锐地发现了这一点,然后他竟然进行了 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×ymodx×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。您的每一段留言,在我们看来都珍贵无比。

  • 41
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值