扩展欧拉定理
欧拉定理,即:若正整数
a
a
a 与
m
m
m 互质,则:
a
φ
(
m
)
≡
1
(
m
o
d
m
)
a^{\varphi(m)} \equiv 1(\bmod m)
aφ(m)≡1(modm)
这里的
φ
(
m
)
\varphi(m)
φ(m) 是欧拉函数,即小于或等于
m
m
m 且与
m
m
m 互质的正整数个数。当
m
m
m 是质数
p
p
p 时,欧拉定理退化成 费马小定理
a
p
−
1
≡
1
(
m
o
d
p
)
a^{p-1} \equiv 1(\bmod p)
ap−1≡1(modp) 。
接下来我们证明欧拉定理:
记小于 n 且与 n 互质的正整数集合为
R = { x 1 , x 2 , ⋯ , x φ ( n ) } R=\left\{x_{1}, x_{2}, \cdots, x_{\varphi(n)}\right\} R={x1,x2,⋯,xφ(n)}
S = { a x 1 % n , a x 2 % n , ⋯ , a x φ ( n ) % n } S=\left\{a x_{1} \% n, a x_{2} \% n, \cdots, a x_{\varphi(n)} \% n\right\} S={ax1%n,ax2%n,⋯,axφ(n)%n}
∀ i ∈ [ 1 , φ ( n ) ] ∵ ( a , n ) = 1 , ( x i , n ) = 1 ∴ ( a x i , n ) = 1 \begin{array}{l} \forall i \in[1, \varphi(n)] \\ \because(a, n)=1,\left(x_{i}, n\right)=1 \\ \therefore\left(a x_{i}, n\right)=1 \end{array} ∀i∈[1,φ(n)]∵(a,n)=1,(xi,n)=1∴(axi,n)=1
由最大公约数的性质可得
(
a
x
i
%
n
,
n
)
=
1
\left(a x_{i} \% n, n\right)=1
(axi%n,n)=1
所以
S
\mathrm{S}
S 中所有元素都与
n
n
n 互质,且都小于
n
n
n 。
又
S
\mathrm{S}
S 中无重复元素
假设
i
≠
j
,
a
x
i
%
n
=
a
x
j
%
n
i \neq j, a x_{i} \% n=a x_{j} \% n
i=j,axi%n=axj%n
即
a
x
i
≡
a
x
j
(
m
o
d
n
)
a x_{i} \equiv a x_{j}(\bmod n)
axi≡axj(modn), 又
(
a
,
n
)
=
1
(a, n)=1
(a,n)=1
∴
x
i
≡
x
j
(
m
o
d
n
)
,
i
=
j
\therefore x_{i} \equiv x_{j}(\bmod n), i=j
∴xi≡xj(modn),i=j ,矛盾!
∴
S
=
R
\therefore S=R
∴S=R
∴
∏
i
=
1
φ
(
n
)
a
x
i
%
n
=
∏
i
=
1
φ
(
n
)
x
i
\therefore \prod_{i=1}^{\varphi(n)} a x_{i} \% n=\prod_{i=1}^{\varphi(n)} x_{i}
∴∏i=1φ(n)axi%n=∏i=1φ(n)xi
∴
∏
i
=
1
φ
(
n
)
a
x
i
≡
∏
i
=
1
φ
(
n
)
x
i
(
m
o
d
n
)
⟹
a
φ
(
n
)
∏
i
=
1
φ
(
n
)
x
i
≡
∏
i
=
1
φ
(
n
)
x
i
(
m
o
d
n
)
\therefore \prod_{i=1}^{\varphi(n)} a x_{i} \equiv \prod_{i=1}^{\varphi(n)} x_{i}(\bmod n) \Longrightarrow a^{\varphi(n)} \prod_{i=1}^{\varphi(n)} x_{i} \equiv \prod_{i=1}^{\varphi(n)} x_{i}(\bmod n)
∴∏i=1φ(n)axi≡∏i=1φ(n)xi(modn)⟹aφ(n)∏i=1φ(n)xi≡∏i=1φ(n)xi(modn)
又
(
∏
i
=
1
φ
(
n
)
x
i
,
n
)
=
1
\left(\prod_{i=1}^{\varphi(n)} x_{i}, n\right)=1
(∏i=1φ(n)xi,n)=1
故
a
φ
(
n
)
≡
1
(
m
o
d
n
)
a^{\varphi(n)} \equiv 1(\bmod n)
aφ(n)≡1(modn)
在算法竞赛中,我们常常会用到它的一个重要的推论:若正整数
a
a
a 与
m
m
m 互质,则
a
b
≡
a
b
m
o
d
φ
(
m
)
(
m
o
d
m
)
a^{b} \equiv a^{b \bmod \varphi(m)}(\bmod m)
ab≡abmodφ(m)(modm)
(这是因为
a
b
=
a
φ
(
m
)
⌊
b
/
φ
(
m
)
⌋
+
b
m
o
d
φ
(
m
)
≡
1
⋅
a
b
m
o
d
φ
(
m
)
(
m
o
d
m
)
a^{b}=a^{\varphi(m)\lfloor b / \varphi(m)\rfloor+b \bmod \varphi(m)} \equiv 1 \cdot a^{b \bmod \varphi(m)}(\bmod m)
ab=aφ(m)⌊b/φ(m)⌋+bmodφ(m)≡1⋅abmodφ(m)(modm) )
利用这个推论,即使
b
b
b 比较大,我们也可以轻松地计算
a
b
m
o
d
m
a^{b} \bmod m
abmodm 的值,但需要满足
a
a
a 与
m
m
m 互质的前提。
为了解决
a
a
a 与
m
m
m 不互质时的此问题,我们引入拓展欧拉定理: 若
b
≥
φ
(
m
)
b \geq \varphi(m)
b≥φ(m) ,则:
a
b
≡
a
b
m
o
d
φ
(
m
)
+
φ
(
m
)
(
m
o
d
m
)
(
∗
)
a^{b} \equiv a^{b \bmod \varphi(m)+\varphi(m)}(\bmod m) \quad(*)
ab≡abmodφ(m)+φ(m)(modm)(∗)
这里仍有前提条件,但影响不大,因为
b
<
φ
(
m
)
b<\varphi(m)
b<φ(m) 时直接用快速幂计算即可。
当***
b
<
φ
(
m
)
b<\varphi(m)
b<φ(m)*** 时的板子
#include <bits/stdc++.h>
using namespace std;
bool large_enough = false; // 判断是否有b >= phi(m)
inline int read(int MOD = 1e9 + 7) // 快速读入稍加修改即可以边读入边取模,不取模时直接模一个大于数据范围的数
{
int ans = 0;
char c = getchar();
while (!isdigit(c))
c = getchar();
while (isdigit(c))
{
ans = ans * 10 + c - '0';
if (ans >= MOD)
{
ans %= MOD;
large_enough = true;
}
c = getchar();
}
return ans;
}
int phi(int n) // 求欧拉函数
{
int res = n;
for (int i = 2; i * i <= n; i++)
{
if (n % i == 0)
res = res / i * (i - 1);
while (n % i == 0)
n /= i;
}
if (n > 1)
res = res / n * (n - 1);
return res;
}
int qpow(int a, int n, int MOD) // 快速幂
{
int ans = 1;
while (n)
{
if (n & 1)
ans = 1LL * ans * a % MOD; // 注意防止溢出
n >>= 1;
a = 1LL * a * a % MOD;
}
return ans;
}
int main()
{
int a = read(), m = read(), phiM = phi(m), b = read(phiM);
cout << qpow(a, b + (large_enough ? phiM : 0), m);
return 0;
}
BSGS算法
大步小步算法(baby step giant step,BSGS),是一种用来求解离散对数(即模意义下对数)的算法,即给 出
a
x
≡
b
(
m
o
d
m
)
a^{x} \equiv b(\bmod m)
ax≡b(modm) 中
a
,
b
,
m
a, b, m
a,b,m 的值(这里保证
a
a
a 和
m
m
m 互质),求解
x
x
x 。
既然保证了
a
a
a 和
m
m
m 互质,那么很容易联想到欧拉定理,我们知道
a
φ
(
m
)
≡
1
(
m
o
d
m
)
a^{\varphi(m)} \equiv 1(\bmod m)
aφ(m)≡1(modm) ,也就说明
a
x
a^{x}
ax 在模
m
m
m 意义下有一个长度为
φ
(
m
)
\varphi(m)
φ(m) 的循环节。既然后面都是循环的,我们只需要考虑
x
≤
φ
(
m
)
x \leq \varphi(m)
x≤φ(m) 的情形就可以 了。这就有了离散对数的朴素算法: 暴力枚举。复杂度
O
(
φ
(
m
)
)
O(\varphi(m))
O(φ(m)) ,因为当
m
m
m 是质数时
φ
(
m
)
=
m
−
1
\varphi(m)=m-1
φ(m)=m−1 , 最坏时间复杂度就是
O
(
m
)
O(m)
O(m) 。
实际上,大步小步算法就是对暴力枚举的一个简单的改进,使用了类似meet in the middle的思想(这个算法 还没有讲,待补)。我们把
x
x
x 拆成
A
t
−
B
A t-B
At−B ,则原式化为
a
A
t
−
B
≡
b
(
m
o
d
m
)
a^{A t-B} \equiv b(\bmod m)
aAt−B≡b(modm) ,即
a
A
t
≡
b
a
B
(
m
o
d
m
)
a^{A t} \equiv b a^{B}(\bmod m)
aAt≡baB(modm) 。然后我们预计算出右侧所有可能的取值,再固定一个
t
t
t ,计算出左边可能的值,当 发现某个值已经在右边出现过,这时的
A
t
−
B
A t-B
At−B 就是我们要求的
x
x
x 。
B可能的取值有
φ
(
m
)
m
o
d
t
\varphi(m) \bmod t
φ(m)modt 个,A可能的取值有
⌊
φ
(
m
)
/
t
⌋
\lfloor\varphi(m) / t\rfloor
⌊φ(m)/t⌋ 个,不难看出,取
t
=
⌈
φ
(
m
)
]
t=\lceil\sqrt{\varphi(m)}]
t=⌈φ(m)] 是最好 的,当然为了避免计算欧拉函数,我们直接取
t
=
⌈
m
⌉
t=\lceil\sqrt{m}\rceil
t=⌈m⌉ ,不难验证,此时取
A
,
B
∈
[
1
,
t
]
A, B \in[1, t]
A,B∈[1,t] 可以保证把
x
∈
[
1
,
m
−
1
]
x \in[1, m-1]
x∈[1,m−1] 全部枚举一遍。时间复杂度为
O
(
m
)
O(\sqrt{m})
O(m) 。
至于如何判定右边的数有没有出现过,用哈希表是最简单的(STL中的 unordered_map 是用哈希表实现的,一般 可以直接使用),如果用 map,复杂度会多一个
log
\log
log 。
ll BSGS(ll a, ll b, ll m)
{
static unordered_map<ll, ll> hs;
hs.clear();
ll cur = 1, t = sqrt(m) + 1;
for (int B = 1; B <= t; ++B)
{
(cur *= a) %= m;
hs[b * cur % m] = B; // 哈希表中存B的值
}
ll now = cur; // 此时cur = a^t
for (int A = 1; A <= t; ++A)
{
auto it = hs.find(now);
if (it != hs.end())
return A * t - it->second;
(now *= cur) %= m;
}
return -1; // 没有找到,无解
}
拓展大步小步算法
上面的算法只针对
a
a
a 和
m
m
m 互质的情形,如果不互质,又该如何计算? 我们尝试把它转化成
a
a
a 和
m
m
m 互质的情 形。
当
x
>
0
x>0
x>0 时,
a
x
≡
b
(
m
o
d
m
)
a^{x} \equiv b(\bmod m)
ax≡b(modm) 等价于
a
x
−
1
a
+
n
m
=
b
a^{x-1} a+n m=b
ax−1a+nm=b ,由斐蜀定理(此处曾提到过)可知,有解的 条件是
b
b
b 是
gcd
(
a
,
m
)
\operatorname{gcd}(a, m)
gcd(a,m) 的倍数。
设
d
=
gcd
(
a
,
m
)
d=\operatorname{gcd}(a, m)
d=gcd(a,m) ,则
a
x
−
1
a
d
+
n
m
d
=
b
d
\frac{a^{x-1} a}{d}+\frac{n m}{d}=\frac{b}{d}
dax−1a+dnm=db ,即
a
d
a
x
−
1
≡
b
d
(
m
o
d
m
d
)
\frac{a}{d} a^{x-1} \equiv \frac{b}{d}\left(\bmod \frac{m}{d}\right)
daax−1≡db(moddm) 。这时如果
gcd
(
a
,
m
d
)
=
1
\operatorname{gcd}\left(a, \frac{m}{d}\right)=1
gcd(a,dm)=1 , 就可以直接用大步小步算法求解了 (左侧多了一个系数,但这显然影响不大,把大步小步算法稍作修改即可), 解出的
x
x
x 加上 1 即是原方程的一个解。否则,可以继续递归下去,直到互质为止。
但是,这样算出来的只保证是原方程的一个解,末必是最小解,因为虽然BSGS求出的是最小解,但我们还进行 了递归层数 (下面称为
c
n
t
c n t
cnt ) 次加 1 ,真正的最小值可能在
[
0
,
c
n
t
)
[0, c n t)
[0,cnt) 间,所以还需要枚举这些数是否为答案。
注意一下特判,本来BSGS是不需要特判的,但是我们的递归条件只对
x
>
0
x>0
x>0 时成立。所以如果
b
≡
1
b \equiv 1
b≡1 直接返 回
x
=
0
x=0
x=0 即可。
为了进一步加快速度,我们用非递归而非递归的方式实现这个算法。这里很容易做到,而且写起来比递归版本更 加简洁。
// 修改版的BSGS,额外带一个系数
ll BSGS(ll a, ll b, ll m, ll k = 1)
{
static unordered_map<ll, ll> hs;
hs.clear();
ll cur = 1, t = sqrt(m) + 1;
for (int B = 1; B <= t; ++B)
{
(cur *= a) %= m;
hs[b * cur % m] = B; // 哈希表中存B的值
}
ll now = cur * k % m;
for (int A = 1; A <= t; ++A)
{
auto it = hs.find(now);
if (it != hs.end()) return A * t - it->second;
(now *= cur) %= m;
}
return -INF; // 这里因为要多次加1,要返回更小的负数
}
ll exBSGS(ll a, ll b, ll m, ll k = 1)
{
ll A = a %= m, B = b %= m, M = m;
if (b == 1) return 0;
ll cur = 1 % m;
for (int i = 0;; i++)
{
if (cur == B) return i;
cur = cur * A % M;
ll d = gcd(a, m);
if (b % d) return -INF;
if (d == 1) return BSGS(a, b, m, k * a % m) + i + 1;
k = k * a / d % m, b /= d, m /= d; // 相当于在递归求解exBSGS(a, b / d, m / d, k * a / d % m)
}
}