快速幂 AcWing 89. a^b c++和Java版

18 篇文章 0 订阅
17 篇文章 0 订阅

题目

AcWing 89. a^b

求 a 的 b 次方对 p 取模的值。

输入格式
三个整数 a,b,p ,在同一行用空格隔开。

输出格式
输出一个整数,表示a^b mod p的值。

数据范围
0 ≤ a,b ≤ 109
1 ≤ p ≤ 109

输入样例:

3 2 7

输出样例:

2

暴力版

对于一个小白来说,最容易想到的就是通过 for 循环来计算 a 的 b 次方,然后再对 p 取模,这也是对题目最直接的体现:

Java

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.StringTokenizer;

public class Main {
	
	public static void main(String[] args) throws IOException {
		InputReader in = new InputReader(System.in);
		PrintWriter out = new PrintWriter(System.out);
		
		long a = in.nextLong();
		long b = in.nextLong();
		long p = in.nextLong();
		
		long res = 1;
		
		for(int i = 0; i < b; i++) {
			res *= a;
		}
		
		out.print(res % p);
		out.flush();
	}
	
	public static class InputReader{
		BufferedReader reader;
		StringTokenizer tokenizer;
		
		public InputReader(InputStream stream) {
			reader = new BufferedReader(new InputStreamReader(stream));
		}
		
		public String next() throws IOException {
			while(tokenizer == null || !tokenizer.hasMoreTokens()) {
				tokenizer = new StringTokenizer(reader.readLine());
			}
			
			return tokenizer.nextToken();
		}
		
		public int nextInt() throws IOException {
			return Integer.parseInt(next());
		}
		
		public Long nextLong() throws IOException {
			return Long.parseLong(next());
		}
	}

}

c++

#include<bits/stdc++.h>

using namespace std;

int main()
{
    long a, b, p;

    scanf("%ld%ld%ld", &a, &b, &p);

    long res = 1;

    for(int i = 0; i < b; i++)
    {
        res *= a;
    }

    printf("%ld", res % p);
    return 0;
}

暴力的方法从逻辑上来说并没有问题,但随着 b 的增长,会产生两个问题,a 的 b次方会变得非常大,受计算机能处理的 数值大小限制,将会没有数据类型能够装下他,不仅如此,循环的次数也会非常多,暴力方法的时间复杂度为 O(n),所以对于本题来说即使数据能装下,在最坏情况下也会TLE。

结合数学优化

首先要提一个先验知识,数论中对于数论取模有以下公式,本篇中不做证明:
(A * B) % p = (A % p * B % p) % p

例如样例中3 * 3 % 7 = 2,可以计算为 (3 % 7* 3 % 7) % 7 = 2

所以可以在每次乘上一个数后都进行一次取余操作,减小数值的大小

Java

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.StringTokenizer;

public class Main {
	
	public static void main(String[] args) throws IOException {
		InputReader in = new InputReader(System.in);
		PrintWriter out = new PrintWriter(System.out);
		
		long a = in.nextLong();
		long b = in.nextLong();
		long p = in.nextLong();
		
		long res = 1;
		
		for(int i = 0; i < b; i++) {
			res *= a;
			res %= p;
		}
		
		out.print(res % p);
		out.flush();
	}
	
	public static class InputReader{
		BufferedReader reader;
		StringTokenizer tokenizer;
		
		public InputReader(InputStream stream) {
			reader = new BufferedReader(new InputStreamReader(stream));
		}
		
		public String next() throws IOException {
			while(tokenizer == null || !tokenizer.hasMoreTokens()) {
				tokenizer = new StringTokenizer(reader.readLine());
			}
			
			return tokenizer.nextToken();
		}
		
		public int nextInt() throws IOException {
			return Integer.parseInt(next());
		}
		
		public Long nextLong() throws IOException {
			return Long.parseLong(next());
		}
	}

}


c++

#include<bits/stdc++.h>

using namespace std;

int main()
{
    long a, b, p;

    scanf("%ld%ld%ld", &a, &b, &p);

    long res = 1;

    for(int i = 0; i < b; i++)
    {
        res *= a;
        res %= p;
    }

    printf("%ld", res % p);
    return 0;
}

数值太大的问题得以解决,但是循环次数过多导致的时间复杂度太大的问题仍然存在,仍需要进行进一步的优化。

快速幂

这时我们就要引入快速幂的概念了

假设此时我们需要计算 3 6 3^{6} 36:
3 6 = 3 ∗ 3 ∗ 3 ∗ 3 ∗ 3 ∗ 3 3^{6} = 3 * 3 * 3 * 3 * 3* 3 36=333333

首先我们先计算 3 2 3^{2} 32,此时将指数变为了原来的一半,此时的循环量也将变为原来的一半;
3 6 = ( 3 ∗ 3 ) ∗ ( 3 ∗ 3 ) ∗ ( 3 ∗ 3 ) = ( 3 2 ) 3 = 9 3 3^{6} = (3 * 3) * (3 * 3) * (3 * 3) = (3^{2})^{3} = 9^{3} 36=(33)(33)(33)=(32)3=93

此时我们仅需计算 9 3 9^{3} 93,但由于此时指数为3,无法接着整除2,所以我们提出一项,计算 9 2 9^{2} 92即可,此时的循环量又将变为原来的一半;
3 6 = [ ( 3 ∗ 3 ) ∗ ( 3 ∗ 3 ) ] ∗ ( 3 ∗ 3 ) = ( 3 2 ) 2 + 1 = ( 3 2 ) 2 ∗ 3 2 = 9 2 ∗ 9 = 8 1 1 ∗ 9 3^{6} = [(3 * 3) * (3 * 3)] * (3 * 3) = (3^{2})^{2+1} = (3^{2})^{2}*3^{2} = 9^{2} * 9 = 81^{1} * 9 36=[(33)(33)](33)=(32)2+1=(32)232=929=8119

此时我们仅需计算 8 1 1 81^{1} 811,但由于此时指数为1,无法接着整除2,所以我们提出一项,计算 8 1 0 81^{0} 810即可,此时计算已经完成;
3 6 = ( 3 ∗ 3 ∗ 3 ∗ 3 ) ∗ ( 3 ∗ 3 ) = 8 1 0 + 1 ∗ 9 = 9 0 ∗ 81 ∗ 9 3^{6} = (3 * 3 * 3 * 3) * (3 * 3) = 81^{0+1} * 9 = 9^{0} *81* 9 36=(3333)(33)=810+19=90819

所以 3 6 = 81 ∗ 9 = 3 4 ∗ 3 2 3^{6} = 81* 9 = 3^{4}*3^{2} 36=819=3432

Java

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.StringTokenizer;

public class Main {
	
	public static void main(String[] args) throws IOException {
		InputReader in = new InputReader(System.in);
		PrintWriter out = new PrintWriter(System.out);
		
		long a = in.nextLong();
		long b = in.nextLong();
		long p = in.nextLong();
		
		long res = 1;
		
		while(b != 0) {	// 当指数还大于 0,说明计算未结束
			if(b % 2 == 1) {	// 当指数为奇数,先提出一项
				res *= a;	// 提出一项
				res %= p;	// (A * B) % p = (A % p * B % p) % p
				b--;	// 提出一项后指数减一
			}
			else {
				a %= p;
				a = (a * a) % p;	// 计算 a 的平方
				b /= 2;	// 指数折半
			}
		}
		
		out.print(res % p);
		out.flush();
	}
	
	public static class InputReader{
		BufferedReader reader;
		StringTokenizer tokenizer;
		
		public InputReader(InputStream stream) {
			reader = new BufferedReader(new InputStreamReader(stream));
		}
		
		public String next() throws IOException {
			while(tokenizer == null || !tokenizer.hasMoreTokens()) {
				tokenizer = new StringTokenizer(reader.readLine());
			}
			
			return tokenizer.nextToken();
		}
		
		public int nextInt() throws IOException {
			return Integer.parseInt(next());
		}
		
		public Long nextLong() throws IOException {
			return Long.parseLong(next());
		}
	}

}



c++

#include<bits/stdc++.h>

using namespace std;

int main()
{
    long a, b, p;

    scanf("%ld%ld%ld", &a, &b, &p);

    long res = 1;

    while(b)    // 当指数还大于 0,说明计算未结束
    {
        if(b % 2)   // 当指数为奇数,先提出一项
        {
            res *= a;   // 提出一项
            res %= p;   // (A * B) % p = (A % p * B % p) % p
            b--;    // 提出一项后指数减一
        }
        else
        {
            a %= p;
            a = (a * a) % p;    // 计算 a 的平方
            b /= 2; // 指数折半
        }
    }

    printf("%ld", res % p);
    return 0;
}


此时的代码已经能够AC了,但是还可以结合位运算来进行 进一步优化

进一步结合二进制优化

对于第一步中的 3 6 3^{6} 36,指数6的二进制为0110B,末位为0,0110&1=0,除以2的操作为右移1位 6 = 0110B >> 1 = 0011B = 3;

对于第二步中的 9 3 9^{3} 93,指数3的二进制为0011B,末位为1,0011&1=1,需要提出一项,除以2的操作为右移1位 3 = 0011B >> 1 = 0001B = 1;

对于第三步中的 8 1 1 81^{1} 811,指数1的二进制为0001B,末位为1,0001&1=1,需要提出一项,除以2的操作为右移1位 1 = 0001B >> 1 = 0000B = 0,计算结束。

而下式能够更直观地体现上述描述,6的二进制为0110B,1和2位上的数为1,正好对应式子中2的次方
3 6 = 81 ∗ 9 = 3 2 2 ∗ 3 2 1 3^{6} = 81* 9 = 3^{2^{2}}*3^{2^{1}} 36=819=322321

具体实现详见 AC代码:

AC代码

Java

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.StringTokenizer;

public class Main {
	
	public static void main(String[] args) throws IOException {
		InputReader in = new InputReader(System.in);
		PrintWriter out = new PrintWriter(System.out);
		
		long a = in.nextLong();
		long b = in.nextLong();
		long p = in.nextLong();
		
		long res = 1;
		
		while(b != 0) {
			if((b & 1) == 1) {
				res *= a;
				res %= p;
			}
			a = a % p;
			a = (a * a) % p;
			b >>= 1;
		}
		
		out.print(res % p);
		out.flush();
	}
	
	public static class InputReader{
		BufferedReader reader;
		StringTokenizer tokenizer;
		
		public InputReader(InputStream stream) {
			reader = new BufferedReader(new InputStreamReader(stream));
		}
		
		public String next() throws IOException {
			while(tokenizer == null || !tokenizer.hasMoreTokens()) {
				tokenizer = new StringTokenizer(reader.readLine());
			}
			
			return tokenizer.nextToken();
		}
		
		public int nextInt() throws IOException {
			return Integer.parseInt(next());
		}
		
		public Long nextLong() throws IOException {
			return Long.parseLong(next());
		}
	}

}

c++

#include<bits/stdc++.h>

using namespace std;

int main()
{
    long a, b, p;

    scanf("%ld%ld%ld", &a, &b, &p);

    long res = 1;

    while(b)
    {
        if(b & 1)
        {
            res *= a;
            res %= p;
        }

        a %= p;
        a = (a * a) % p;
        b >>= 1;
    }

    printf("%ld", res % p);

    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值