数学-快速幂
Posted 闫鸿宇
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数学-快速幂相关的知识,希望对你有一定的参考价值。
从一个简单的问题说起:
给出整数m,n和p,要求计算(m ^ n) % p的结果。
#include <iostream>
using namespace std;
int main()
long long m, n, p;
cin >> m >> n >> p;
long long ans = 1;
for (long long i = 0; i < n; i++)
ans = ans * m;
cout << ans << "\\n";
return 0;
这个程序似乎正确了,但是存在严重问题:
<1>.m或n太大,极容易溢出.
<2>.如果n的值太大,时间消耗O(n)代价较大.
首先解决溢出的问题:
显然:
(a * b) % c = ((a % c) * (b % c)) % c.
这样,就可以把程序改写为如下形式:
但是,如果n的值太大,时间消耗O(n)代价太大,这个问题如何解决呢?
#include <iostream>
using namespace std;
int main()
long long m, n, p;
cin >> m >> n >> p;
long long ans = 1;
for (long long i = 0; i < n; i++)
ans = ((ans % p) * (m % p)) % p;
cout << ans << "\\n";
return 0;
乘方快速幂:
假设要计算 m^10,m^10 = (m^5) ^ 2 = (m * (m ^ 2) ^ 2) ^ 2.
也就是说,要计算m ^ n,有:
那么,程序就变成了:
#include <iostream>
using namespace std;
int main()
long long m, n, p;
cin >> m >> n >> p;
long long ans = 1;
while (n)
if (n % 2 != 0)
ans = ((ans % p) * (m % p)) % p;
n = n / 2;
m = ((m % p) * (m % p)) % p;
cout << ans << "\\n";
return 0;
但是,对于这个程序,我们仍可以继续对其优化:
首先介绍一下 按位与运算(&) 与 右移运算(>>):
<1>.按位与运算:
对于两个二进制数,它们按位与运算的结果是: 对于每一位,如果两个数的这一位同时为1,那么按位与的结果便是1,否则为0,最后将结果转化为十进制,就是我们想要的答案了。 对于一个整数,如果它是奇数,那么它的二进制表示的最低位为1,否则为0,那么对于奇数而言,其按位与1的结果是1,对于偶数而言,其按位与1的结果是0,由此我们可以通过判断一个整数按位与1的结果来判断其是偶数还是奇数.
<2>.右移运算:
同样是对2进制数进行处理,将所有位置上的数字右移,高位补0:如5:101,右移一位为010,结果是2。则:对于一个整数而言,右移一位,相当于其除以2并向下取整。
我们可以根据这两个运算来初步优化程序:
即将 n % 2 != 0 改为 n & 1 == 1,将 n = n / 2 改为 n = n >> 1.
#include <iostream>
using namespace std;
int main()
long long m, n, p;
cin >> m >> n >> p;
long long ans = 1;
while (n)
if (n & 1)
ans = ((ans % p) * (m % p)) % p;
n = n >> 1;
m = ((m % p) * (m % p)) % p;
cout << ans << "\\n";
return 0;
对于m ^ 0,结果为1,1 % 1 == 0,所以,我们应该要防止这种特殊情况,即在进行乘方运算之前,先将ans % p:
#include <iostream>
using namespace std;
int main()
long long m, n, p;
cin >> m >> n >> p;
long long ans = 1 % p;
while (n)
if (n & 1)
ans = ((ans % p) * (m % p)) % p;
n = n >> 1;
m = ((m % p) * (m % p)) % p;
cout << ans << "\\n";
return 0;
因为C++内置的最高整数类型是64位,若运算 (a ^ b) % p中的三个变量a,b,p都在10^18级别,则不存在一个可供强制转化的128位整数类型,我们需要一些特殊的处理办法:
进行乘方运算之前,先让m对p取模一次:
#include <iostream>
using namespace std;
int main()
long long m, n, p;
cin >> m >> n >> p;
long long ans = 1 % p;
m %= p;
while (n)
if (n & 1)
ans = ((ans % p) * (m % p)) % p;
n = n >> 1;
m = ((m % p) * (m % p)) % p;
cout << ans << "\\n";
return 0;
这样就是最优的形式了。
下面给出几道相关的练习题:
Raising Modulo Numbers
我们可以计算每一项a^b的值,然后将其加起来作为结果:
#include <iostream>
#define i64 long long
i64 qpow(i64 a, i64 b, i64 p)
i64 ans = 1 % p;
a %= p;
while (b)
if (b & 1)
ans = ((ans % p) * (a % p)) % p;
b >>= 1;
a = ((a % p) * (a % p)) % p;
return ans;
int main()
int t; std::cin >> t;
while (t--)
i64 M;
std::cin >> M;
i64 H, ans = 0;
std::cin >> H;
for (int i = 0; i < H; i++)
i64 A, B;
std::cin >> A >> B;
ans = ((ans % M) + (qpow(A, B, M) % M)) % M;
std::cout << ans << "\\n";
return 0;
Pseudoprime numbers
题意:
输入p 和 a,如果p不是质数,并且a>1并且(a^p) % p == a % p,那么输出yes,否则输出no
参考代码:
#include <iostream>
using namespace std;
bool isprime(long long n)
if (n < 2)
return false;
for (int i = 2; i <= n / i; i++)
if (n % i == 0)
return false;
return true;
long long qpow(long long m, long long n, long long p)
long long ans = 1 % p;
while (n)
if (n & 1)
ans = ((ans % p) * (m % p)) % p;
n = n >> 1;
m = ((m % p) * (m % p)) % p;
return ans;
int main()
long long p, a;
while (cin >> p >> a && p && a)
if (isprime(p) == false && qpow(a, p, p) == a % p && a > 1)
cout << "yes\\n";
else
cout << "no\\n";
return 0;
方阵快速幂:
LightOJ - 1282 - Leading and Trailing(数学技巧,快速幂取余)
链接:
https://vjudge.net/problem/LightOJ-1282
题意:
You are given two integers: n and k, your task is to find the most significant three digits, and least significant three digits of nk.
思路:
后三位快速幂取余,考虑前三位。
(n^k)可以表示为(a*10^m)即使用科学计数法。
对两边取对数得到(k*log10(n) = log10(a)+m)
则x = log10(a)是k*log10(n)的小数部分。
a = pow(10, x).就是科学计数法的前面部分。
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<math.h>
#include<vector>
#include<map>
using namespace std;
typedef long long LL;
const int INF = 1e9;
const int MAXN = 1e6+10;
const int MOD = 1e9+7;
LL n, k;
LL PowMod(LL a, LL b)
{
LL res = 1;
while(b)
{
if (b&1)
res = res*a%1000;
a = a*a%1000;
b >>= 1;
}
return res;
}
int main()
{
int t, cnt = 0;
scanf("%d", &t);
while(t--)
{
printf("Case %d:", ++cnt);
scanf("%lld%lld", &n, &k);
double v = 1.0*k*log10(n);
v -= (LL)v;
LL r1 = (LL)(pow(10, v)*100);
LL r2 = PowMod(n, k);
printf(" %lld %03lld
", r1, r2);
}
return 0;
}
以上是关于数学-快速幂的主要内容,如果未能解决你的问题,请参考以下文章
整数快速乘法/快速幂+矩阵快速幂+Strassen算法 (转)
AtCoder Beginner Contest 199 F - Graph Smoothing(图的邻接矩阵幂的意义,数学期望,矩阵快速幂)