文章目录
1 欧拉函数
欧拉函数的作用:
欧拉定理:
若a与n互质则
a
Ψ
(
n
)
≡
1
(
m
o
d
n
)
a^{\Psi(n)} \equiv 1(mod n)
aΨ(n)≡1(modn)
当n是质数时就有了
a
p
−
1
≡
1
(
m
o
d
n
)
(
费马小定理)
a^{p - 1} \equiv 1(mod n) (费马小定理)
ap−1≡1(modn)(费马小定理)
1.1 公式法求欧拉函数
Ψ
(
n
)
\Psi(n)
Ψ(n)表示1~n中与n互质的数的个数
分解质因数后
证明:用到了容斥原理
- 从1到N中去掉p1,p2……pk的所有倍数
- 加上所有pi * pj的倍数(多减了一次的数)
- 减去所有pi * pj * pk的倍数
- 加上所有4个质数的倍数
- ……
1.1.1 模板
时间复杂度是: n \sqrt{n} 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);
while (x % i == 0) x /= i;
}
if (x > 1) res = res / x * (x - 1);
return res;
}
1.1.2 例题
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010;
int n;
int phi(int x)
{
int res = x;
for (int i = 2; i <= x / i; i ++)
{
if (x % i == 0)
{
while (x % i == 0) x /= i;
res = res / i * (i - 1);// 这里要先除后乘防止乘后爆int
}
}
if (x > 1) res = res / x * (x - 1);
return res;
}
int main()
{
cin >> n;
while (n -- )
{
int x;
scanf("%d", &x);
printf("%d\n", phi(x));
}
return 0;
}
1.2 筛法求欧拉函数
利用线性筛法求出1~n每一个数的欧拉函数
当primes[j] 是 i 的最小质因子是(在满足(i % primes[j] )的break条件里)
推导过程
e
u
l
e
r
[
i
]
=
i
∗
(
1
−
1
p
1
)
∗
(
1
−
1
p
2
)
∗
.
.
.
∗
(
1
−
1
p
k
)
euler[ i ] = i * (1 - \frac{1}{p_1}) * (1 - \frac{1}{p_2}) * ... * (1 - \frac{1}{p_k})
euler[i]=i∗(1−p11)∗(1−p21)∗...∗(1−pk1)
e
u
l
e
r
[
i
∗
p
r
i
m
e
[
j
]
]
=
(
i
∗
p
r
i
m
e
[
j
]
)
∗
(
1
−
1
p
1
)
∗
(
1
−
1
p
2
)
∗
.
.
.
∗
(
1
−
1
p
k
)
euler[i * prime[j]] = (i * prime[j]) * (1 - \frac{1}{p_1}) * (1 - \frac{1}{p_2}) * ... * (1 - \frac{1}{p_k})
euler[i∗prime[j]]=(i∗prime[j])∗(1−p11)∗(1−p21)∗...∗(1−pk1)
两式就多一项prime[j]
当primes[j]不是最小质因子时要在判断是否是之后
添
e
u
l
e
r
[
i
]
=
i
∗
(
1
−
1
p
1
)
∗
(
1
−
1
p
2
)
∗
.
.
.
∗
(
1
−
1
p
k
)
euler[ i ] = i * (1 - \frac{1}{p_1}) * (1 - \frac{1}{p_2}) * ... * (1 - \frac{1}{p_k})
euler[i]=i∗(1−p11)∗(1−p21)∗...∗(1−pk1)
e
u
l
e
r
[
i
∗
p
r
i
m
e
[
j
]
]
=
(
i
∗
p
r
i
m
e
[
j
]
)
∗
(
1
−
1
p
1
)
∗
(
1
−
1
p
2
)
∗
.
.
.
∗
(
1
−
1
p
k
)
∗
(
1
−
1
p
r
i
m
e
[
j
]
)
euler[i * prime[j]] = (i * prime[j]) * (1 - \frac{1}{p_1}) * (1 - \frac{1}{p_2}) * ... * (1 - \frac{1}{p_k}) * (1 - \frac{1}{prime[j]})
euler[i∗prime[j]]=(i∗prime[j])∗(1−p11)∗(1−p21)∗...∗(1−pk1)∗(1−prime[j]1)
p
r
i
m
e
[
j
]
∗
(
1
−
1
p
r
i
m
e
[
j
]
)
prime[j] * ( 1 - \frac{1}{prime[j]})
prime[j]∗(1−prime[j]1) =
p
r
i
m
e
[
j
]
−
1
prime[j] - 1
prime[j]−1 之后就多了这一项
1.2.1 模板
打印longlong 型是用 printf("%lld", res)
呜呜
时间复杂度是O(n)
int primes[N], cnt; // primes[]存储所有素数
int euler[N]; // 存储每个数的欧拉函数
bool st[N]; // st[x]存储x是否被筛掉
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);
}
}
}
1.2.2 例题
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int primes[N], cnt = 0;
int euler[N];
bool st[N];
void get_euler(int x)
{
euler[1] = 1;// 第一个数为计入其中提前初始化
for (int i = 2; i <= x; i ++)
{
if (!st[i]){// 当i不是质数时
primes[cnt ++] = i;
euler[i] = i - 1;// 质数与它前面的都互质
}
for (int j = 0; primes[j] <= x / 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;
LL res = 0;
get_euler(n);
for (int i = 1; i <= n; i ++) res += euler[i];
printf("%lld", res);
return 0;
}
2 快速幂
2.1 直接求快速幂
快速幂就是快速求出:
a
k
a^{k}
ak mod p 的值
- 当幂是偶数时将内部数平方再计算
- 当幂是奇数是将这个数提出来在按偶数计算
这样就将O(n) 优化成O(logn)
下面思路不予考虑 下面思路不予考虑 下面思路不予考虑
时间复杂度是O( l o g k logk logk)
1 <= a, p, k <= 1 0 9 10^9 109
思路是反复平方法
首先预处理出:
a
2
0
a^{2^{0}}
a20 mod p,
a
2
1
a^{2^{1}}
a21 mod p,
a
2
2
a^{2^{2}}
a22 mod p, ……
a
2
l
o
g
k
a^{2^{logk}}
a2logk mod p
a^k
=
a
2
0
a^{2^{0}}
a20 mod p *
a
2
1
a^{2^{1}}
a21 mod p *
a
2
2
a^{2^{2}}
a22 mod p * …… *
a
2
l
o
g
k
a^{2^{logk}}
a2logk mod p
= a 2 x 1 + 2 x 2 + … … 2 x t a^{2^{x1} + 2 ^ {x2} + …… 2 ^ {xt}} a2x1+2x2+……2xt
将k换成二进制即可 k -> 2的次方之和
每一个数都是上一个数的平方模p
2.1.1 模板
时间复杂度是O( l o g k logk logk)
求 t^k mod p,时间复杂度 O(logk)。
int qmi(int t, int k, int p)
{
int res = 1 % p;
while (k)
{
if (k&1) res = res * t % p;// b%2==1 => k&1 当幂是奇数是将提出一个数在按偶数形式计算
t = t * t % p;// 偶数平方在计算
k >>= 1;// 幂除以2
}
return res;
}
2.1.2 例题
// 通过将a^k % p 普通要将a 循环n次相乘 O(n)的复杂度
// 但是当我们将a 进行二进制表示之后求出二进制中为1的项,而将这些项相乘(因为幂运算幂是相加的)就是结果log(n)的复杂度
// 将a二进制预处理的方法是后面一项是前面一项的平方mod p
#include <iostream>
using namespace std;
typedef long long LL;
int qmi(int a, int k, int p)
{
LL res = 1;
while (k)
{
if (k & 1) res = res*a % p;
a = a * (LL)a % p;
k >>= 1;
}
return res;
}
int main ()
{
int n;
cin >> n;
while (n --)
{
int a, k, p;
scanf ("%d%d%d", &a, &k, &p);
printf("%d\n", qmi(a, k, p));
}
return 0;
}
2.2 利用快速幂求逆元
逆元就是将除法转换成乘法,以上就称x是b mod p的逆元
给定的p是质数的话就是联想到了费马小定理了
如何找到b的逆元呢?
- 只需要找到 b * x ≡ \equiv ≡ 1 (mod p)
- 这样满足上式的x根据费马小定理就可以的到x = b p − 2 b ^ {p - 2} bp−2
- 当p是b的倍数是无解这是余数为0不可能为1
- 当p不是质数时就需要使用扩展欧几里得算法
2.2.1 例题
/*
只有当q为质数时才能用这个方法求逆元(费马小定理)一般情况下用扩展欧几里得算法
*/
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
LL qmi(int t, int k, int q)
{
LL res = 1 % q;
while (k)
{
if (k & 1) res = res * t % q;
t = (LL) t * t % q;
k >>= 1;
}
return res;
}
int main()
{
int n;
cin >> n;
while (n -- ){
int t, q;
scanf("%d%d", &t, &q);
if (t % q == 0) puts("impossible");// 存在乘法逆元的充分条件是t 与 q 互质所以要判断
else printf("%lld\n", qmi(t, q - 2, q));
}
return 0;
}
3 扩展欧几里得算法
裴蜀定理:有一对正整数a, b, 那么一定存在非零整数x, y, 使得ax + by = (a, b)(最大公约数)
- 边界情况是a和0的最大公因数是a,此时x = 1,y = 0
a % b = a - (a / b) * b
带入就会有(其中d是a和b的最大公约数)
exgcd(b, a % b, x, y) 参数的位置与传入的位置对应
b
∗
x
1
+
(
a
−
(
a
/
b
)
∗
b
)
∗
y
1
b*x_1 + (a-(a/b)*b)*y_1
b∗x1+(a−(a/b)∗b)∗y1
= b ∗ x 1 + a ∗ y 1 – ( a / b ) ∗ b ∗ y 1 b*x_1 + a*y1 – (a/b)*b*y_1 b∗x1+a∗y1–(a/b)∗b∗y1
=
a
∗
y
1
+
b
∗
(
x
1
–
a
/
b
∗
y
1
)
=
g
c
d
发现
x
=
y
1
,
y
=
x
1
–
a
/
b
∗
y
1
a*y_1 + b*(x_1 – a/b*y_1) = gcd 发现 x = y_1 , y = x_1 – a/b*y_1
a∗y1+b∗(x1–a/b∗y1)=gcd发现x=y1,y=x1–a/b∗y1
当x, y传参数是互换就成了
x
=
x
1
,
y
=
y
1
−
a
/
b
∗
x
1
x = x_1 , y = y_1 - a / b * x_1
x=x1,y=y1−a/b∗x1
这样就可以直接传递x, y对应的不需要交换exgcd(b, a % b, y, x)// 记忆:x不变传递顺序的y - a / b * x
x, y是根据与a, b相连的系数看出来的
递归之后就会有a的系数不变,b的系数变成上述
当递归到最后一层(a, b) = (b, r) 最后一层就是b和r的情况下的x,y返回到(a, b)的系数
3.1 模板
// 求x, y,使得ax + by = gcd(a, b)
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;
}
3.2 例题
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int exgcd(int a, int b, int &x, int &y)
{
if (!b){// 边界条件 b为零那么a与0的最大公因数是a
x = 1, y = 0;
return a;
}
int d = exgcd(b, a % b, y, x);// 递归后后y的值就变了后面给它改回来
y = y - a / b * x;
return d;
}
int main()
{
int n;
cin >> n;
while (n --)
{
int a, b, x, y;
scanf("%d%d", &a, &b);
exgcd(a, b, x, y);
printf("%d %d\n", x, y);
}
return 0;
}
4 中国剩余定理
前提条件:
m
1
,
m
2
…
…
m
n
两两互质
m_1, m_2……m_n两两互质
m1,m2……mn两两互质
证明:
构造x的基础解系 与当前模m_1的那个为1,其他模m_i的为0就是
a
1
∗
M
1
∗
M
1
−
1
a1*M_1*M_1^{-1}
a1∗M1∗M1−1
其中
M
1
中不含
m
1
M_1中不含m_1
M1中不含m1所以x模就是0,同时利用
M
1
−
1
M_1^{-1}
M1−1来使模这个
m
1
m_1
m1等于表达式的值
a
1
a_1
a1
这样就得到了通解