寒假集训学习记录 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

posted @ 2021-01-17 20:33  臭小鬼zm  阅读(54)  评论(0编辑  收藏  举报