本节内容:
1.欧拉函数
2.快速幂
3.扩展欧几里得算法
4.中国剩余定理
1.欧拉函数:
<i>求一个数的欧拉函数:
时间复杂度:O(sqrt(n))
证明:
代码:
int phi(int x)
{
int res = x;
for (int i = 2; i <= x / i; i ++ ) // 分解质因数模版
{
if(x % i == 0)
{
res = res / i * (i - 1); // 公式里1 - 1/p1 转化成 (p1 - 1) / p1;
}
while (x % i == 0) x /= i;
}
if (x > 1) res = res / x * (x - 1);
return res;
}
<ii> 求1到n中每个数的欧拉函数之和(线性筛求欧拉函数):
时间复杂度: O(n)
思路:
我们可以用线性筛法把数字分成质数和合数
质数的欧拉函数就是phi[i] = i - 1;
合数时,有两种情况
第一种情况时,pj是i的最小质因子,pj小于i的所有质因子,所以pj同时也是i * pj的最小质因子,就有了上面的式子,而 i * (1 - 1/pj) ... (1 - 1/pk)正好是i的欧拉函数,所以简化成phi[i] * pj
第二种情况时,pj 不是i的质因子,又因为 pj * i 的质因子有 pj 和 i 的所有质因子,所以就是第二个式子,而同理把 i * (1 - 1/p1) ... (1 - 1/pk) 简化成phi[i]
代码:
int get_eulers(int n)
{
euler[1] = 1; // 初始化
for (int i = 2; i <= n; i ++ ) // 线筛模版
{
if (!st[i])
{
primes[cnt ++ ] = i;
euler[i] = i - 1; //从1到质数i中与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);
}
}
LL res = 0;
for (int i = 1; i <= n; i ++ ) res += euler[i];
return res;
}
<iii>欧拉定理:
对于任意正整数 a 和 n,如果 a 和 n 互质,则有:a^ϕ(n) ≡ 1 (mod n),就是a的ϕ(n)次方模n为1
证明:
特殊情况时:
2.快速幂:
时间复杂度:O(logk)
快速的求出 a ^ k mod p 的结果,其中 1 <= a, p, k <= 10^9
思路:
代码:
typedef long long LL;
//a^k % p
int qmi(int a, int k, int p)
{
int res = 1;
while (k) // 对k进行二进制化
{
// 如果k的二进制表示的第零位是1,则乘上当前的a
if (k & 1) res = (LL)res * a % p;
k >>= 1; // 去掉最后的一位,右移一位
a = (LL)a * a % p; // 更新a,因为去掉一位,a要从a^(2^0),变成a^(2^1),以此类推到a^(2^2),a^(2^logk)
}
return res;
}
用法:快速幂求逆元:
思路:
p是质数,p是质数,p是质数,不是质数就不行了,费马定理就不能用了
代码:
typedef long long LL;
int get_qmi(int a, int k, int p)
{
int res = 1;
while (k) // 快速幂模版
{
if (k & 1) res = (LL)res * a % p;
k >>= 1;
a = (LL)a * a % p;
}
if (a % p) cout << res << endl; // 如果a不是p的倍数,输出逆元,否则就没有
else cout << "impossible" << endl;
}
// main里面的 get_qmi(a, p - 2, p); // 求a的p - 2次方
3.扩展欧几里得算法:
思路:
代码:
int exgcd(int a, int b, int &x, int &y) // 记得加引用符号,不然在输出xy时,他的值还是0
{
if (!b)
{
x = 1, y = 0;
return a;
}
int x1, y1;
/* 第一种,比较好理解
int d = exgcd(b, a % b, x1, y1);
x = y1;
y = x1 - a/b * y1; */
// 第二种更简便
int d = exgcd(b, a % b, y, x);
y = y - a/b * x;
return d;
}
如果我们求出了x, y,那我们就求出了所有解,因为d是a和b的公约数,所以b/d,a/d一定是整数,所以a, b的系数一定是整数,并且展开后 ab/d消掉了,等式依旧满足
应用:求解线性同余方程:
思路:
代码:
#include <iostream>
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 = 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"); // b不是d的倍数,一定无解
else printf("%d\n", (LL)x * (b/d) % m); // 否额输出解
}
return 0;
}
4.中国剩余定理:
这个好难......
思路:
对于一般的定理
例题:
题目:
代码(扩展):
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL; // 数的范围较大用longlong存
LL exgcd(LL a, LL b, LL &x, LL &y) // 扩展欧几里得算法模版 求ax + by = gcd(a, b);
{
if (!b)
{
x = 1, y = 0;
return a;
}
LL d = exgcd(b, a % b, y, x);
y = y - a / b * x;
return d;
}
int main()
{
int n;
cin >> n;
bool has_answer = true; // bool存储是否有解
LL a1, m1; // 第一个方程的系数
cin >> a1 >> m1;
for (int i = 0; i < n - 1; i ++ ) // 进行n - 1次合并
{
LL a2, m2;
cin >> a2 >> m2;
LL k1, k2;
LL d = exgcd(a1, a2, k1, k2);
if ((m2 - m1) % d) // 看是否是a1, a2的最大公因数的倍数,是则有解,反之无解
{
has_answer = false;
break;
}
k1 *= (m2 - m1) / d; // 因为我们求的是ax + by = d, 而方程是 a1k1 - a2k2 = m2 - m1,所以两边同时乘以(m2 -m1)/d转换成所求方程
LL t = a2 / d;
k1 = (k1 % t + t) % t; // 让其在一个合适的范围,由通解知让其保持在 0 到 a2 /d 之间
m1 = a1 * k1 + m1; // 合并后的m1就是a1 * k1 + m1
a1 = a1 / d * a2; // 合并后的a1就是a1和a2的最小公倍数
}
if (has_answer)
{
cout << (m1 % a1 + a1) % a1 << endl; // 求最小x,就等于 x mod a1
}
else puts("-1");
return 0;
}
经典代码(m1,m2......mk 互质时):
int main()
{
LL res = 0, mul = 1;
cin >> n;
for(int i = 0; i < n; i ++)
{
cin >> a[i] >> b[i];
mul *= a[i];
}
LL x, y;
for(int i = 0; i < n; i ++)
{
LL Mi = cnt / a[i];
exgcd(Mi, a[i], x, y); // 用扩展欧几里得算法求逆元
if (x < 0) x += m[i]; // 确保 逆元是正数
res += b[i] * Mi % cnt * x % cnt; // 带入公式
}
cout << res % cnt << " ";
}