题目
求 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=3∗3∗3∗3∗3∗3
首先我们先计算
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=(3∗3)∗(3∗3)∗(3∗3)=(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=[(3∗3)∗(3∗3)]∗(3∗3)=(32)2+1=(32)2∗32=92∗9=811∗9
此时我们仅需计算
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=(3∗3∗3∗3)∗(3∗3)=810+1∗9=90∗81∗9
所以 3 6 = 81 ∗ 9 = 3 4 ∗ 3 2 3^{6} = 81* 9 = 3^{4}*3^{2} 36=81∗9=34∗32
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=81∗9=322∗321
具体实现详见 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;
}