前言
本章博客将介绍欧拉函数、快速幂、扩展欧几里得算法的常用模板,有些思路是基于上一篇博客《数论 - 质数和约数》,在文中已经标注出来,如果难以理解,可以先查看上一篇博客。
欧拉函数部分将介绍:求解欧拉函数、筛法求欧拉函数
快速幂部分将介绍:求解快速幂、快速幂求逆元
扩展欧几里得算法部分将介绍:扩展欧几里得算法求裴蜀定理系数、线性同余方程
一、欧拉函数
欧拉函数ϕ(N)表示1~n中与n互质的数的个数
1.求解欧拉函数
- 题目:给定 n 个正整数 ai,请你求出每个数的欧拉函数。
- 分解质因数:N = P1a1 * P2a2 * P3a3 * …… * Pkak
- 欧拉函数:ϕ(N)=N(1-1/p1)(1-1/p2)(1-1/p3)……(1-1/pk)
- 要运用分解质因子的模板(在上一篇博客《数论-质数与约数》中提到)
#include <iostream>
using namespace std;
int phi(int x)
{
//分解质因子的变形
int res = x;
for (int i = 2; i <= x / i; i ++ )
if (x % i == 0)
{
res = res / i * (i - 1);
while (x % i == 0) x /= i;
}
if (x > 1) res = res / x * (x - 1);
return res;
}
int main()
{
int n;
cin >> n;
while (n -- )
{
int x;
cin >> x;
cout << phi(x) << endl;
}
return 0;
}
2.筛法求欧拉函数
- 题目:给定一个正整数 n ,求 1∼n 中每个数的欧拉函数之和。
- 基于线性筛法(在上一篇博客《数论-质数与约数》中提到)求素数,基于线性筛法的三种情况进行扩展,求欧拉函数
- 如果一个数p是质数,那么ϕ( p ) = p-1,因为比p小的所有数都和p互质
- 如果i mod pj == 0(pj是i的最小质因子),则ϕ(i)=i(1-1/p1)(1-1/p2)(1-1/p3)……(1-1/pk),而ϕ(pj*i)=pj*i(1-1/p1)(1-1/p2)(1-1/p3)……(1-1/pk),所以ϕ(pj*i)=pjϕ(i)
- 如果i mod pj != 0(pj只是pj*i的最小质因子),则ϕ(i)=i(1-1/p1)(1-1/p2)(1-1/p3)……(1-1/pk),而ϕ(pj*i)=pj*i(1-1/p1)(1-1/p2)(1-1/p3)……(1-1/pk)(1-1/pj),所以ϕ(pj*i)=pjϕ(i)(1-1/pj)
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 1000010;
int primes[N], cnt;
int euler[N]; //每个数的欧拉函数
bool st[N];
void get_eulers(int n)
{
euler[1] = 1;
for (int i = 2; i <= n; i ++ )
{
if (!st[i])
{
primes[cnt ++ ] = i;
euler[i] = i - 1;
}
for (int j = 0; primes[j] <= n / i; j ++ )
{
int t = primes[j] * i;
st[t] = true;
if (i % primes[j] == 0)
{
euler[t] = euler[i] * primes[j];
break;
}
euler[t] = euler[i] * (primes[j] - 1);
}
}
}
int main()
{
int n;
cin >> n;
get_eulers(n);
LL res = 0;
for (int i = 1; i <= n; i ++ ) res += euler[i];
cout << res << endl;
return 0;
}
二、快速幂
通过预处理和反复平方法,在时间复杂度为O (logN)下快速求出某个数的某次幂,暴力时间复杂度为O (N),快了很多
1.求解快速幂
- 题目:给定 n 组 ai,bi,pi,对于每组数据,求出 a的b次方 mod p 的值。
- 求ak mod p
- 预处理算出:a的20次方~a的2logk每个数mod p的值
- 选取部分预处理数相乘(相当于可以拼接出ak)mod p即可得到答案,选取步骤通过k的二进制数来确定,取1则选取,取0则不取
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
LL qmi(int a, int b, int p)
{
LL res = 1 % p;
while (b)
{
if (b & 1) res = res * a % p; //该位是1则选取该预处理结果相乘
a = a * (LL)a % p; //a是不断变化的每一位预处理结果
b >>= 1; //右移1位
}
return res;
}
int main()
{
int n;
scanf("%d", &n);
while (n -- )
{
int a, b, p;
scanf("%d%d%d", &a, &b, &p);
printf("%lld\n", qmi(a, b, p));
}
return 0;
}
2.快速幂求逆元
- 题目:给定 n 组 ai,pi,其中 pi 是质数,求 ai 模 pi 的乘法逆元,若逆元不存在则输出
impossible。 - 若整数 b,m互质,并且对于任意的整数 a,如果满足 b|a,则存在一个整数 x,使得 a/b≡a*x(mod m)),则称 x 为 b 的模 m 乘法逆元,记为 b−1(modm)。
- b 存在乘法逆元的充要条件是 b 与模数 m 互质。当模数 m 为质数时,bm−2 即为 b 的乘法逆元。
- 逆元作用:我们必须在中间过程中进行求余,否则数字太大,但是(a / b) % p != (a%p / b%p) %p ,所以我们要将除法转化为乘法,来满足求余时的“分配律”
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
LL qmi(int a, int b, int p)
{
LL res = 1;
while (b)
{
if (b & 1) res = res * a % p;
a = a * (LL)a % p;
b >>= 1;
}
return res;
}
int main()
{
int n;
scanf("%d", &n);
while (n -- )
{
int a, p;
scanf("%d%d", &a, &p);
if (a % p == 0) puts("impossible"); //由于p是质数,所以a和p不互质是a一定是p的倍数
else printf("%lld\n", qmi(a, p - 2, p));
}
return 0;
}
三、扩展欧几里得算法
欧几里得算法即上一篇博客《数论-质数与约数》中求最大公约数的辗转相除法,即(a,b) = (b,a mod b),扩展欧几里得算法是在此基础上的扩展
1.扩展欧几里得算法求裴蜀定理系数
- 题目:给定 n 对正整数 ai,bi,对于每对数,求出一组 xi,yi,使其满足 aixi+biyi=gcd(ai,bi)。
- 裴蜀定理:对于任意正整数a、b,一定存在整数x、y(x,y不同时为0),使得ax + by = (a,b),(a,b)表示a和b的最大公约数
- 如果b == 0,则x = 1,y = 0
- 如果 b != 0,则最大公约数d = exgcd(b, a % b, y, x),x和y颠倒仅仅是为了方便计算,此时 by + (a mod b)x = d,则ax + b(y - (a/b)下取整x) = d,可以看出颠倒xy后化简出的式子依旧为ax,所以x是不用更改的,更加方便计算,而y = y - (a/b)下取整x
- 用欧几里得算法递归求取,xy用引用求取
#include <iostream>
#include <algorithm>
using namespace std;
int exgcd(int a, int b, int &x, int &y)
{
if (!b)
{
x = 1, y = 0;
return a;
}
int d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
int main()
{
int n;
scanf("%d", &n);
while (n -- )
{
int a, b;
scanf("%d%d", &a, &b);
int x, y;
exgcd(a, b, x, y);
printf("%d %d\n", x, y);
}
return 0;
}
2.线性同余方程
- 题目:给定 n 组数据 ai,bi,mi,对于每组数求出一个 xi,使其满足 ai*xi≡bi(mod mi),如果无解则输出
impossible - ax≡b(mod m)等价于ax = my + b。等价于令y = -y,ax + my = b,即转化为为扩展欧几里得算法求裴蜀定理系数(上一题)
- 则b如果是(a,m)的倍数就一定成立,满足裴蜀定理,相当于把等式ax + my = b左右同时乘以b/(a,m)
- 则b如果不是(a,m)的倍数,一定不成立
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
int exgcd(int a, int b, int &x, int &y)
{
if (!b)
{
x = 1, y = 0;
return a;
}
int d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
int main()
{
int n;
scanf("%d", &n);
while (n -- )
{
int a, b, m;
scanf("%d%d%d", &a, &b, &m);
int x, y;
int d = exgcd(a, m, x, y);
if (b % d) puts("impossible");
else printf("%d\n", (LL)b / d * x % m);
}
return 0;
}
942

被折叠的 条评论
为什么被折叠?



