快速幂算法详解

参考以下资料,理解了之后自己尝试写一篇

     快速幂算法(全网最详细地带你从零开始一步一步优化)


目录

首先我们从一个问题入手

        为什么会WA

        为什么会TLE

快速幂算法

优化

进一步优化

结束


首先我们从一个问题入手

      P1226 【模板】快速幂||取余运算

      题意很简单,输入只有一行三个整数,分别代表 a,b,p.求a ^ b mod p

      题目乍一看非常容易,思路简单,一个循环搞定.

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+7;

int f_pow(int x,int n,int p){//x为底数,n为指数,p为模数 
	int ans=1;
	for (int i=1;i<=n;i++){
		ans=ans*x;
	}

	return ans%p;
} 

void solve(){
	int x,n,p;
	cin >> x >> n >> p;
	int st=clock();
//	cout << f_pow(x,n,p) << '\n';
	cout << x << "^" << n << ' ' << "mod " << p << "=" <<f_pow(x,n,p);
	int ed=clock();
//	cout << (double)(ed-st)/CLOCKS_PER_SEC << '\n';
	
}
signed main(){
	int t=1;
//	cin>>t;
	while (t--){
		solve();
	}
	return 0;
}

        写完啪交一发,然后惊喜的发现又WA又T,怎么回事呢?

为什么会WA

        看了一眼数据范围,2^{31},好直接爆long long,那么应该怎么做呢?再看一遍我们的代码,很显然啊,先求出结果再取模,但是现在数据太大爆long long 求不出结果怎么办?

        那么,我们首先来了解一下同余定理:

(a+b)%p=(a%p + b%p) % p;

(a-b)%p=(a%p - b%p+p) % p;

(a*b)%p=(a%p * b%p) % p;

        我们看第三个,求两个数乘积的取模,就是对两个数分别取模再乘起来再取模,更进一步,我们要对一串连续乘积的结果取模 (a*b*c)%p,即求(a%p * b%p * c%p)%p.即只要对每次乘积结果取模即可,代码表示为:

int f_pow(int x,int n,int p){//x为底数,n为指数,p为模数 
	int ans=1;
	for (int i=1;i<=n;i++){
		ans=ans*x%p;
	}
	return ans%p;
} 

        好修改完再交一发,惊喜的发现WA的地方成功被我们消除了,说明我们我们这么修改时对的.

        好解决完了WA,然后就是T了,那么

为什么会TLE

        那么我们来分析一下咱这代码的时间复杂度,我们就用了一个循环,显然复杂度为O(n),考虑最坏的情况,n最大为2^{31},我们来测试一下代码跑了多久(2^{31}就用1e9代替了,差不多大小的)

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+7;

int f_pow(int x,int n,int p){//x为底数,n为指数,p为模数 
	int ans=1;
	for (int i=1;i<=n;i++){
		ans=ans*x%p;
	}
	return ans%p;
} 

void solve(){
	int x,n,p;
	cin >> x >> n >> p;
	int st=clock();//记录起始时间 
//	cout << f_pow(x,n,p) << '\n';
	cout << x << "^" << n << ' ' << "mod " << p << "=" <<f_pow(x,n,p) << '\n';
	int ed=clock();//记录运算结束时间 
	cout << (double)(ed-st)/CLOCKS_PER_SEC << '\n';//ed与st的差值即为程序运行花费的CPU时钟单元数量,再除每秒CPU有多少个时钟单元,即为程序耗时
	
}
signed main(){
	int t=1;
//	cin>>t;
	while (t--){
		solve();
	}
	return 0;
}

        发现:

         跑了近5秒....

       那么怎么办呢,这就要隆重推出本次的主题了:快速幂算法

快速幂算法

        快速幂算法能帮我们算出指数非常大的幂,传统的求幂算法之所以时间复杂度非常高(为O(n)),就是因为当指数n非常大的时候,需要执行的循环操作次数也非常大。而快速幂可以将复杂度降为O(log n),所以我们快速幂算法的核心思想就是每一步都把指数分成两半,而相应的底数做平方运算。这样不仅能把非常大的指数给不断变小,所需要执行的循环次数也变小,而最后表示的结果却一直不会变。先来看一个例子:

        2^{10}=2*2*2*2*2*2*2*2*2*2

        将指数尽量变小, 以减少循环次数,这里为10

        2^{10}=(2^2)^5 = 4^5 这里为5

        此时指数由10缩减一半变成了5,而底数变成了原来的平方,求2^{10}要用10次操作,而4^5

    只需5次操作,直接减少一半计算量,当n很大时效果会更好.

        我们对 5继续分解,由于不是偶数,不能直接硬拆,考虑将指数减1变为偶数再乘

        4^5=4^4*4

        好,继续拆分

        4^4=(4^2)^2=16^2=(16^2)^1=256^1

        可得:2^{10}=4^5=(4^2)^2*4=16^2*4=256*4=1024

        我们能够发现,最后的结果是256*4,而4是怎么产生的?是不是当指数为奇数5时,此时底数为4。那256又是怎么产生的呢?是不是当指数为奇数1时,此时的底数为256。所以我们能发现一个规律:最后求出的幂结果实际上就是在变化过程中所有当指数为奇数时底数的乘积。

         代码实现:

int f_pow(int x,int n,int p){//x为底数,n为指数,p为模数 
	int ans=1;
	while (n){
		if (n%2==0){
			n=n/2;
			x=x*x%p;
		}
		else{
			ans=ans*x%p;
			n--;
			n=n/2;
			x=x*x%p;
		}
	}
	return ans%p;
} 

        因为我们每次都是折半,股时间复杂度为O(log n) 直接交一发,好,发现既没有T也没有WA

优化

        题目虽然过了,但是我们代码还能继续优化,上面的代码太丑了.

        看上面代码发现if语句里又很多重复的语句,如:n=n/2;x=x*x%p;因此我们可以考虑合并他们

        然后下面有个n--,我们的目的是将其转化为偶数再除2,这里可以直接用n/2

        故经过简单优化后的代码为:

int f_pow(int x,int n,int p){//x为底数,n为指数,p为模数 
	int ans=1;
	while (n){
		if (n%2==1){
			ans=ans*x%p;
		}
		n=n/2;
		x=x*x%p;
	}
	return ans%p;
} 

进一步优化

        判断奇偶我们考虑二进制,末尾为1为奇,0为偶,那么我们可以通过 与 运算(&)来写 与 1即可判断奇偶性

        n=n/2,也考虑二进制,这不就是把二进制表示的n往右移动一位吗

        因此,进一步优化之后的终极代码表现为:

int f_pow(int x,int n,int p){//x为底数,n为指数,p为模数 
	int ans=1;
	while (n){
		if (n&1) ans=ans*x%p;
		x=x*x%p;
		n>>=1;
	}
	return ans;
} 

结束


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值