寒假集训学习记录 1-17
今天的学习真是让人心态炸裂,这个期末的项目作业真是过于突然。嗯,让人处于一种特别想要学习特别想写但是啥也不会的挫败之中,还是太菜了,QAQ。
所以还是入正题,今天来说一下快速幂算法,之前好像遇到过但是一直超时不知道怎么优化,下面附上一道题:
可以先看看这个题,想想自己的思路。
普通的最容易想到的方法(嗯,就是for循环)时间复杂度非常大,需要的运行时间就算算出来了交上去也肯定是无法通过的,而且还有就是可能根本得不到正确答案,早就溢出,结果为0。还有题目上给的取模运算,目的是什么呢,因为这种指数型的增长往往快到不可思议,一部小心就溢出了,指数小还好,大的话用最大的long long也放不下,也不是没想到过用字符串来存,但是这样好像怎么算都是一个问题了(嗷,我太难了,感觉在想桃子)……
还好在网上了解到取模的运算法则,如下:
1.(a+b)%c=(a%c+b%c)%c 2.(a-b)%c=(a%c-b%c)%c 3.(a*b)%c=(a%c*b%c)%c
关于这道题的话,看最后一个就行,同时也可以知道(a*b*c)%d=(a%d*b%d*c%d)%d,那么在之前那个for循环的基础上只需要对每一次乘积都提前取模,就可以得到很好的效果,但是虽然能够得到正确的答案,但是在指数很大很大的时候,运行时间非常大,一点也不适用,那么就到了今天的主要内容,快速幂算法。
由于之前的代码用for循环指数越大运行时间就越长,所以我么们的目的就是减少指数的大小,增大底数,这样就可以得到很好的效果,如下:
1 3^10=3*3*3*3*3*3*3*3*3*3 2 =9^5//此时时间复杂度已经降低到了一半了,但是5却不能在继续缩小一半
于是在指数为奇数时,可以先提一个底数出来(记得最后别忘了),此时9^5=9*9^4=9*81^2=9^1*6561^1;这样操作后的结果目的就是直到指数为1时,运行时间已经大大减少了,看看此时的代码:
1 #include <iostream> 2 #include <cmath> 3 using namespace std; 4 long long mi(long long a,long long b){ 5 long long ans=1; 6 while(b>0){ 7 if(b%2==0){ 8 //指数为偶数时 9 b= b/2; 10 a=a*a%1000; 11 } 12 else { 13 //指数为奇数时 14 b=b-1; 15 ans=ans*a%1000; 16 b=b/2; 17 a=a*a%1000; 18 } 19 } 20 return ans; 21 } 22 int main(){ 23 long long a,b; 24 while(cin>>a>>b){ 25 if(a==0&&b==0) break; 26 cout<<mi(a,b)<<endl; 27 } 28 return 0; 29 }
这个时候的代码可用性已经很好了,但是我们观察到在函数里面有重复代码,所以还可以进一步优化一下:
long long ans=1; while(b>0){ if(b%2==1) ans=ans*a%1000; b/=2; a=(a*a)%1000; }
emmm,然后又在网上发现了位运算这个好东西,例如:power&1。因为如果power为偶数,则其二进制表示的最后一位一定是0;如果power是奇数,则其二进制表示的最后一位一定是1。将他们分别与1的二进制做“与”运算,得到的就是power二进制最后一位的数字了,是0则为偶数,是1则为奇数。例如5是奇数,则5&1=1;而6是偶数,则6&1=0;因此奇偶数的判断就可以用“位运算”来替换了。
5 : 0 0 0 0 0 1 0 1 1 : 0 0 0 0 0 0 0 1 6 : 0 0 0 0 0 1 1 0
同样,对于power=power/2来说,也可以用更快的“位运算”进行替代,我们只要把power的二进制表示向右移动1位就能变成原来的一半了。
6 : 0 0 0 0 0 1 1 0 3 : 0 0 0 0 0 0 1 1
然后我们就会得到一个运算时间极短的程序,like this:
1 #include <iostream> 2 #include <cmath> 3 using namespace std; 4 long long mi(long long a,long long b){ 5 long long ans=1; 6 while(b>0){ 7 if(b&1)/*等价于if(b%2==1)*/{ 8 ans=ans*a%1000; 9 } 10 b>>=1; 11 a=(a*a)%1000; 12 } 13 return ans; 14 } 15 int main(){ 16 long long a,b; 17 while(cin>>a>>b){ 18 if(a==0&&b==0) break; 19 cout<<mi(a,b)<<endl; 20 } 21 return 0; 22 }
嗯,到位,然后每天一句:我是小废物。加油!
最后谢谢大佬的超详细解析,感兴趣可以仔细看看。https://blog.csdn.net/qq_19782019/article/details/85621386?utm_source=app&app_version=4.5.0