目录
3 欧拉函数
互质:公因数只有 1 1 1的两个非零自然数,叫做互质数。
欧拉函数的定义: 1 ∼ N 1∼N 1∼N中与 N N N互质的数的个数被称为欧拉函数,记为 ϕ ( N ) \phi(N) ϕ(N)。
由算数基本定理中, N = P 1 α 1 ⋅ P 2 α 2 ⋅ ⋅ ⋅ P k α k N = P_1^{\alpha_1}·P_2^{\alpha_2}···P_k^{\alpha_k} N=P1α1⋅P2α2⋅⋅⋅Pkαk,则 ϕ ( N ) = N × P 1 − 1 P 1 × P 2 − 1 P 2 × . . . × P k − 1 P k \phi(N) = N \times \frac{P_1 - 1}{P_1} \times \frac{P_2 - 1}{P_2} \times...\times \frac{P_k - 1}{P_k} ϕ(N)=N×P1P1−1×P2P2−1×...×PkPk−1
证明 (容斥原理): N N N的质因子只有 P 1 、 P 2 、 . . . 、 P k P_1、P_2、...、P_k P1、P2、...、Pk
- 从 1 ∼ N 1∼N 1∼N中去掉 P 1 、 P 2 、 . . . 、 P k P_1、P_2、...、P_k P1、P2、...、Pk的所有倍数,会多去掉一些;
- 加上 P i × P j P_i \times P_j Pi×Pj的倍数;
- 减去所有 P i × P j × P k P_i \times P_j \times P_k Pi×Pj×Pk的倍数;
- …
上面步骤形成的公式为:
N − N P 1 − N P 2 − . . . − N P k + N P 1 P 2 + N P 1 P 3 + . . . − N P 1 P 2 P 3 − N P 1 P 2 P 4 − . . . + N P 1 P 2 P 3 P 4 + . . . . . . \begin{aligned} N &- \frac{N}{P_1} - \frac{N}{P_2} -...-\frac{N}{P_k}\\ &+\frac{N}{P_1P_2} + \frac{N}{P_1P_3} + ... \\ &-\frac{N}{P_1P_2P_3}-\frac{N}{P_1P_2P_4}- ...\\ &+\frac{N}{P_1P_2P_3P_4}+...\\ &...\\ \end{aligned} N−P1N−P2N−...−PkN+P1P2N+P1P3N+...−P1P2P3N−P1P2P4N−...+P1P2P3P4N+......将 ϕ ( N ) = N × P 1 − 1 P 1 × P 2 − 1 P 2 × . . . × P k − 1 P k \phi(N) = N \times \frac{P_1 - 1}{P_1} \times \frac{P_2 - 1}{P_2} \times...\times \frac{P_k - 1}{P_k} ϕ(N)=N×P1P1−1×P2P2−1×...×PkPk−1展开即得上式。
欧拉函数的应用
欧拉定理:若
a
a
a与
n
n
n互质,那么
a
ϕ
(
n
)
≡
1
(
m
o
d
n
)
a^{\phi(n)} \equiv 1(mod \space n)
aϕ(n)≡1(mod n)
例如
a
=
5
、
n
=
6
a = 5、n = 6
a=5、n=6,那么有
ϕ
(
6
)
=
2
\phi(6)=2
ϕ(6)=2,则
5
2
m
o
d
6
=
1
5^2 \space mod \space 6 = 1
52 mod 6=1 。
欧拉定理推论(费马小定理):若
a
a
a与
p
p
p互质且
p
p
p为质数的时候,
(
a
ϕ
(
p
)
=
a
p
−
1
)
≡
1
(
m
o
d
p
)
(a^{\phi(p)} = a^{p - 1}) \equiv 1(mod \space p)
(aϕ(p)=ap−1)≡1(mod p)
欧拉定理证明:设 1 ∼ n 1∼n 1∼n中和 n n n互质的数为 a 1 , a 2 , . . . , a ϕ ( n ) a_1,a_2,...,a_{\phi(n)} a1,a2,...,aϕ(n),因为 a a a与 n n n互质,那么有 a a 1 , a a 2 , . . . , a a ϕ ( n ) aa_1,aa_2,...,aa_{\phi(n)} aa1,aa2,...,aaϕ(n)同样与 n n n互质,且这些数两两互不相同(证明见下面)。因此有 a ϕ ( n ) ⋅ ( a 1 ⋅ a 2 ⋅ . . . ⋅ a ϕ ( n ) ) ≡ ( a 1 ⋅ a 2 ⋅ . . . ⋅ a ϕ ( n ) ) ( m o d n ) a ϕ ( n ) ≡ 1 ( m o d n ) \begin{aligned} &a^{\phi(n)} · (a_1 · a_2 ·... ·a_{\phi(n)}) \equiv (a_1 · a_2 ·... ·a_{\phi(n)})(mod \space n)\\ &a^{\phi(n)} \equiv 1(mod \space n)\\ \end{aligned} aϕ(n)⋅(a1⋅a2⋅...⋅aϕ(n))≡(a1⋅a2⋅...⋅aϕ(n))(mod n)aϕ(n)≡1(mod n)
证明 a a 1 , a a 2 , . . . , a a ϕ ( n ) aa_1,aa_2,...,aa_{\phi(n)} aa1,aa2,...,aaϕ(n)这些数两两互不相同:若有 a a i ≡ a a j ( m o d n ) aa_i \equiv aa_j(mod\space n) aai≡aaj(mod n),那么就有 a ( a i − a j ) ≡ 0 ( m o d n ) a(a_i-a_j)\equiv 0(mod\space n) a(ai−aj)≡0(mod n),因为 a a a与 n n n互质,也就有 a i ≡ a j a_i \equiv a_j ai≡aj,矛盾。
3.1 朴素法求欧拉函数
分解质因数时间复杂度 O ( N ) O(\sqrt{N}) O(N),这个题有 n n n个数,所以这个题的时间复杂度为 O ( n N ) O(n\sqrt{N}) O(nN)
#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;
}
3.2 筛法求欧拉函数
根据线性筛法求质数的方法,来求欧拉函数。
代码中几个地方解释:
- 第18行:当 i i i为一个质数时,它的欧拉函数为 i − 1 i - 1 i−1;
- 第23行: i m o d P j = 0 i \space mod \space P_j = 0 i mod Pj=0,这个时候 p r i m e s [ j ] primes[j] primes[j]为 i i i的最小质因子,所以 P j ⋅ i P_j·i Pj⋅i的质因数和 i i i的质因数相等,那么有欧拉函数 ϕ ( i ) = i × P 1 − 1 P 1 × P 2 − 1 P 2 × . . . × P k − 1 P k ϕ ( P j ⋅ i ) = P j × i × P 1 − 1 P 1 × P 2 − 1 P 2 × . . . × P k − 1 P k \begin{aligned} &\phi(i) = i \times \frac{P_1 - 1}{P_1} \times \frac{P_2 - 1}{P_2} \times...\times \frac{P_k - 1}{P_k}\\ &\phi(P_j · i) = P_j \times i \times \frac{P_1 - 1}{P_1} \times \frac{P_2 - 1}{P_2} \times...\times \frac{P_k - 1}{P_k}\\ \end{aligned} ϕ(i)=i×P1P1−1×P2P2−1×...×PkPk−1ϕ(Pj⋅i)=Pj×i×P1P1−1×P2P2−1×...×PkPk−1因此有 ϕ ( P j ⋅ i ) = P j × ϕ ( i ) \phi(P_j · i) = P_j \times \phi(i) ϕ(Pj⋅i)=Pj×ϕ(i)
- 第27行: i m o d P j ≠ 0 i \space mod \space P_j \ne 0 i mod Pj=0,这时 P j P_j Pj是 i × P j i \times P_j i×Pj的最小质因子,但 P j P_j Pj不是 i i i的最小质因子。那么有欧拉函数 ϕ ( i ) = i × P 1 − 1 P 1 × P 2 − 1 P 2 × . . . × P k − 1 P k ϕ ( P j ⋅ i ) = P j × i × P 1 − 1 P 1 × P 2 − 1 P 2 × . . . × P k − 1 P k × P j − 1 P j \begin{aligned} &\phi(i) = i \times \frac{P_1 - 1}{P_1} \times \frac{P_2 - 1}{P_2} \times...\times \frac{P_k - 1}{P_k}\\ &\phi(P_j · i) = P_j \times i \times \frac{P_1 - 1}{P_1} \times \frac{P_2 - 1}{P_2} \times...\times \frac{P_k - 1}{P_k} \times \frac{P_j - 1}{P_j}\\ \end{aligned} ϕ(i)=i×P1P1−1×P2P2−1×...×PkPk−1ϕ(Pj⋅i)=Pj×i×P1P1−1×P2P2−1×...×PkPk−1×PjPj−1因此有 ϕ ( P j ⋅ i ) = P j × ϕ ( i ) × P j − 1 P j = ( P j − 1 ) × ϕ ( i ) \begin{aligned} \phi(P_j · i) &= P_j \times \phi(i) \times \frac{P_j - 1}{P_j}\\ &=(P_j - 1) \times \phi(i)\\ \end{aligned} ϕ(Pj⋅i)=Pj×ϕ(i)×PjPj−1=(Pj−1)×ϕ(i)
时间复杂度: O ( n ) O(n) O(n)
#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;
}
3.3 可见的点
假设存在一个点 ( x 0 , y 0 ) (x_0,y_0) (x0,y0),该点与坐标原点 ( 0 , 0 (0,0 (0,0 之间的直线斜率为 y 0 x 0 \frac{y_0}{x_0} x0y0,且直线方程为 y = y 0 x 0 x y = \frac{y_0}{x_0}x y=x0y0x。如果该点为可见点,即该点为从坐标原点沿直线出发所能到达的第一个点。
算法思路:
( x 0 , y 0 ) (x_0,y_0) (x0,y0) 为直线上的第一个点 ⟺ \Longleftrightarrow ⟺ x 0 、 y 0 x_0、y_0 x0、y0 互质
证明:
- 假设 x 0 、 y 0 x_0、y_0 x0、y0 不互质,那么存在 ( x 0 , y 0 ) = d > 0 (x_0, y_0) = d > 0 (x0,y0)=d>0,即存在点 ( x 0 d , y 0 d ) (\frac{x_0}{d}, \frac{y_0}{d}) (dx0,dy0) 存在直线上且位于 ( x 0 , y 0 ) (x_0,y_0) (x0,y0) 左下方,那么 ( x 0 , y 0 ) (x_0,y_0) (x0,y0) 便不是第一个可见点。
- 假设 x 0 、 y 0 x_0、y_0 x0、y0 互质,且存在点 ( x 0 ′ , y 0 ′ ) ({x_0}', {y_0}') (x0′,y0′) 在直线上, x 0 ′ < x 0 、 y 0 ′ < y 0 {x_0}' < x_0、{y_0}' < y_0 x0′<x0、y0′<y0,那么有 y 0 ′ x 0 ′ = y 0 x 0 \frac{{y_0}'}{{x_0}'} = \frac{y_0}{x_0} x0′y0′=x0y0 。因为 x 0 、 y 0 x_0、y_0 x0、y0 互质,也就是说分数 y 0 x 0 \frac{y_0}{x_0} x0y0 已经最简,因此不能能存在一个更小的 ( x 0 ′ , y 0 ′ ) ({x_0}', {y_0}') (x0′,y0′) 在点 x 0 、 y 0 x_0、y_0 x0、y0 左下方,即如果直线上 x 0 、 y 0 x_0、y_0 x0、y0 互质,它就一定是直线上的第一个可见点。
因此该问题转换为了:求解 0 ≤ x 、 y ≤ N 0 \le x、y \le N 0≤x、y≤N中有多少对 ( x , y ) (x,y) (x,y) 满足 x 、 y x、y x、y 互质?
由图关于直线 y = x y = x y=x 对称性,可以只考虑直线 y = x y = x y=x 下面(不包括直线上的唯一一个点 ( 1 , 1 ) (1,1) (1,1) )的所有情况。枚举每一个横坐标 x = 2 , 2 , 3 , . . . , N x = 2,2,3,..., N x=2,2,3,...,N ,可以得到与每一个横坐标互质的纵坐标个数为 ϕ ( x ) \phi(x) ϕ(x),所以在直线下方与所有横纵标互质的纵坐标个数为 ∑ x = 2 N ϕ ( x ) \sum_{x = 2}^N \phi(x) ∑x=2Nϕ(x),因此整个图的可见点个数为 2 ∑ x = 2 N ϕ ( x ) + 1 2\sum_{x = 2}^N \phi(x) +1 2x=2∑Nϕ(x)+1
#include <cstdio>
using namespace std;
const int N = 1010;
int primes[N], cnt;
bool st[N];
int phi[N];
void init(int n) {
phi[1] = 1;
for (int i = 2; i <= n; i++) {
if (!st[i]) {
primes[cnt++] = i;
phi[i] = i - 1;
}
for (int j = 0; primes[j] * i <= n; j++) {
st[i * primes[j]] = true;
if (i % primes[j] == 0) {
phi[i * primes[j]] = phi[i] * primes[j];
break;
}
phi[i * primes[j]] = phi[i] * (primes[j] - 1);
}
}
}
int main() {
init(N - 1);
int n, m; scanf("%d", &m);
for (int T = 1; T <= m; T++) {
scanf("%d", &n);
int res = 1;
for (int i = 1; i <= n; i++) res += phi[i] * 2;
printf("%d %d %d\n", T, n, res);
}
return 0;
}
3.4 最大公约数
算法思路:
因为 ( x , y ) = p (x,y) = p (x,y)=p,其中 x 、 y ∈ [ 1 , N ] x、y \in [1,N] x、y∈[1,N]且 p p p为质数,所以有 ( x p , y p ) = 1 (\frac{x}{p}, \frac{y}{p} ) = 1 (px,py)=1,记 x ′ = x p 、 y ′ = y p x' = \frac{x}{p}、y' = \frac{y}{p} x′=px、y′=py,那么原问题就转换为:有多少个 ( x ′ , y ′ ) (x',y') (x′,y′),其中 1 ≤ x ′ 、 y ′ ≤ ⌊ N p ⌋ 1 \le x'、y' \le \left \lfloor \frac{N}{p} \right \rfloor 1≤x′、y′≤⌊pN⌋,满足 ( x ′ , y ′ ) = 1 (x',y') = 1 (x′,y′)=1,即 x ′ 、 y ′ x'、y' x′、y′互质。
对于 p p p可以枚举 [ 1 , N ] [1,N] [1,N]的所有质数 p p p,到这里这个问题就和上一题的处理方法基本一样。只是这里 ⌊ N p ⌋ \left \lfloor \frac{N}{p} \right \rfloor ⌊pN⌋是变化的,为了快速求解 ϕ ( 1 ) + ϕ ( 2 ) + . . . + ϕ ( ⌊ N p ⌋ ) \phi(1) + \phi(2) + ... + \phi(\left \lfloor \frac{N}{p} \right \rfloor) ϕ(1)+ϕ(2)+...+ϕ(⌊pN⌋),可以在筛法求解欧拉函数之后求一个 ϕ \phi ϕ的前缀和数组 s [ ] s[] s[],其中定义 ϕ ( 1 ) = 0 \phi(1) = 0 ϕ(1)=0。
#include <cstdio>
typedef long long LL;
const int N = 1e7 + 10;
int primes[N], cnt;
bool st[N];
int phi[N];
LL s[N]; // phi前缀和数组 假定phi(1) = 0
void init(int n) {
for (int i = 2; i <= n; i++) {
if (!st[i]) {
primes[cnt++] = i;
phi[i] = i - 1;
}
for (int j = 0; primes[j] * i <= n; j++) {
st[primes[j] * i] = true;
if (i % primes[j] == 0) {
phi[i * primes[j]] = phi[i] * primes[j];
break;
}
phi[i * primes[j]] = phi[i] * (primes[j] - 1);
}
}
for (int i = 1; i <= n; i++) s[i] = s[i - 1] + phi[i];
}
int main() {
int n; scanf("%d", &n);
init(n);
LL res = 0;
for (int i = 0; i < cnt; i++) {
int p = primes[i];
res += s[n / p] * 2 + 1;
}
printf("%lld\n", res);
return 0;
}
4 快速幂 (欧拉降幂)
下面所有的 l o g log log 均以2为底
用于在时间复杂度为 O ( l o g k ) O(log^k) O(logk)以内,快速求出 a k m o d p a^k \space mod \space p ak mod p 的结果,其中 1 ≤ a , p , k ≤ 1 0 9 1 \le a, p, k \le 10^9 1≤a,p,k≤109。
原理:
先预处理出
l
o
g
k
log^k
logk个值:
a
2
0
m
o
d
p
、
a
2
1
m
o
d
p
、
.
.
.
、
a
2
l
o
g
k
m
o
d
p
a^{2^0} \space mod \space p、a^{2^1} \space mod \space p\space、...、\space a^{2^{log^k}} \space mod \space p
a20 mod p、a21 mod p 、...、 a2logk mod p,其中每个数的求解方法为
a
2
0
=
a
1
a
2
1
=
a
2
0
⋅
2
=
(
a
2
0
)
2
a
2
2
=
(
a
2
1
)
2
.
.
.
a
2
l
o
g
k
=
(
a
2
l
o
g
k
−
1
)
2
\begin{aligned} &a^{2^0} = a^1\\ &a^{2^1} = a^{2^0 · 2} = (a^{2^0})^2\\ &a^{2^2} = (a^{2^1})^2\\ &...\\ &a^{2^{log^k}} = (a^{2^{log^{k-1}}})^2\\ \end{aligned}
a20=a1a21=a20⋅2=(a20)2a22=(a21)2...a2logk=(a2logk−1)2所以从前往后平方
k
k
k次就可以求出这
l
o
g
k
log^k
logk个数。
然后将 a k a^k ak拆开成 a k = a 2 x 1 ⋅ a 2 x 2 ⋅ . . . ⋅ a 2 x t = a 2 x 1 + 2 x 2 + . . . + 2 x t \begin{aligned} a^k &= a^{2^{x_1}} · a^{2^{x_2}} · ... ·a^{2^{x_t}}\\ &=a^{2^{x_1} + 2^{x_2} + ... + 2^{x_t}}\\ \end{aligned} ak=a2x1⋅a2x2⋅...⋅a2xt=a2x1+2x2+...+2xt即将 k k k表示成二进制形式 k = 2 x 1 + 2 x 2 + . . . + 2 x t k = 2^{x_1} + 2^{x_2} + ... + 2^{x_t} k=2x1+2x2+...+2xt对于其中的每个数,可以从预处理出来的数中找到。
时间复杂度 O ( l o g k ) O(log^k) O(logk)
4.1 快速幂
#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;
a = (LL) a * a % p;
b >>= 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;
}
4.2 快速幂求逆元 (将除法变成乘法)
乘法逆元: 若整数 b , m b,m b,m互质,并且对于任意的整数 a a a,如果满足 b ∣ a b|a b∣a ( a b \frac{a}{b} ba为一个整数),则存在一个整数 x x x,使得 a / b ≡ a × x ( m o d m ) a/b≡a \times x(mod \space m) a/b≡a×x(mod m)则称 x x x为 b b b模 m m m的乘法逆元,记为 b − 1 ( m o d m ) b^{−1}(mod \space m) b−1(mod m)。对上式变形有 b ⋅ b − 1 ≡ 1 ( m o d m ) b\space·\space b^{-1} \equiv 1 (mod \space m) b ⋅ b−1≡1(mod m)
对于题目就转换为,找一个数 x x x使 b ⋅ x ≡ 1 ( m o d p ) b\space·\space x \equiv 1 (mod \space p) b ⋅ x≡1(mod p),且由题可知 p p p为质数,即 p ≥ 2 p \ge 2 p≥2。如果 b b b和 p p p互为质数,由费马小定理可知 b p − 1 = b ⋅ b p − 2 ≡ 1 ( m o d p ) b^{p-1} = b · b^{p-2} \equiv 1 (mod \space p) bp−1=b⋅bp−2≡1(mod p)所以对于一个质数 b b b而言,它的乘法逆元即为 b p − 2 b^{p-2} bp−2。
b b b存在乘法逆元的充要条件是 b b b与模数 m m m互质。当模数 m m m为质数时, b m − 2 b^{m−2} bm−2即为 b b b的乘法逆元。
对于本题就是求一个快速幂 a p − 2 ( m o d p ) a^{p-2} (mod \space p) ap−2(mod 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");
else printf("%lld\n", qmi(a, p - 2, p));
}
return 0;
}
4.3 序列的第k个数
算法思路:
- 除了全等数列(数列中的每个数值都相等),一个数列的前三项会不会既是等差数列,又是等比数列?
假设数列前三项为 a 、 b 、 c a、b、c a、b、c:
如果该数列为等差数列,则应该满足 a + c = 2 b a + c = 2b a+c=2b;
如果该数列为等比数列,则应该满足 a c = b 2 ac = b^2 ac=b2;
将 a = 2 b − c a = 2b - c a=2b−c带入 a c = b 2 ac = b^2 ac=b2可得 c ( 2 b − c ) = b 2 ⇒ b 2 − 2 b c + c 2 = 0 ⇒ ( b − c ) 2 = 0 ⇒ b = c \begin{aligned} &c(2b - c) = b^2\\ \Rightarrow \space & b^2 -2bc + c^2 = 0\\ \Rightarrow \space & (b - c)^2 = 0\\ \Rightarrow \space & b = c\\ \end{aligned} ⇒ ⇒ ⇒ c(2b−c)=b2b2−2bc+c2=0(b−c)2=0b=c即有 a = c = b a = c = b a=c=b。因此,如果一个数列既为等差数列又为等比数列,那么它一定是一个全等数列。也就是说,除了全等数列之外,不可能存在一个数列既是等差数列,又是等比数列。 - 情况1:如果该数列为一个等差数列,则公差 d = b − a d = b - a d=b−a,第 k k k项为 a + d ( k − 1 ) a + d(k-1) a+d(k−1);
- 情况2:如果该数列为一个等比数列,则公比为 p = b a p = \frac{b}{a} p=ab,第 k k k项为 a p k − 1 = a ( b a ) k − 1 ap^{k-1} = a(\frac{b}{a})^{k-1} apk−1=a(ab)k−1,其中因为序列为整数数列,所以可以知道 b a \frac{b}{a} ab 一定是一个整数。
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int mod = 200907;
int qmi(int a, int k) {
int res = 1;
while (k) {
if (k & 1) res = (LL) res * a % mod;
a = (LL) a * a % mod;
k >>= 1;
}
return res;
}
int main() {
int n; cin >> n;
while (n--) {
int a, b, c, k; cin >> a >> b >> c >> k;
if (a + c == b * 2) cout << (a + (b - a) * (LL) (k - 1)) % mod << endl;
else cout << (LL) a * qmi(b / a, k - 1) % mod << endl;
}
return 0;
}
4.4 越狱
算法思路:
发生越狱的情况 = 每个犯人的所有信仰(全集) - 不发生越狱的情况
发生越狱的情况 = m n − m ( m − 1 ) n − 1 m^n - m(m - 1)^{n-1} mn−m(m−1)n−1,使用快速幂即可求解。
注意最终结果取余后应该为正数!
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int mod = 100003;
int qmi(int a, LL k) {
int res = 1;
while (k) {
if (k & 1) res = (LL) res * a % mod;
a = (LL) a * a % mod;
k >>= 1;
}
return res;
}
int main() {
int m; LL n; cin >> m >> n;
cout << (qmi(m, n) - (LL) m * qmi(m - 1, n - 1) % mod + mod) % mod << endl;
return 0;
}
4.5 矩阵乘法
矩阵乘法通常与快速幂结合考察
矩阵乘法基本模板:假设有矩阵 P A × B 、 Q B × C P_{A \times B}、Q_{B \times C} PA×B、QB×C,两个矩阵相乘 P A × B × Q B × C = R A × C P_{A \times B} \times Q_{B \times C} = R_{A \times C} PA×B×QB×C=RA×C代码如下:
for (int i = 1; i <= A; i ++ )
for (int j = 1; j <= C; j ++ )
for (int k = 1; k <= B; k ++ )
R[i][j] += P[i][k] * Q[k][j];
矩阵乘法重要性质:结合律,即 A ⋅ B ⋅ C = A ⋅ ( B ⋅ C ) A \cdot B \cdot C = A \cdot (B \cdot C) A⋅B⋅C=A⋅(B⋅C)
注意:矩阵乘法没有交换律
该性质的应用:假设需要求解 A ⋅ n x ⋅ x ⋅ x ⋯ x ⏞ = A ⋅ ( n x ⋅ x ⋅ x ⋯ x ⏞ ) = A ⋅ x n A \cdot \begin{matrix} n \\ \overbrace{ x \cdot x \cdot x \cdots x } \end{matrix} = A \cdot (\begin{matrix} n \\ \overbrace{ x \cdot x \cdot x \cdots x } \end{matrix}) = A \cdot x^n A⋅nx⋅x⋅x⋯x =A⋅(nx⋅x⋅x⋯x )=A⋅xn,这里可以考虑快速幂。因此可以使用矩阵结合律和快速幂在 O ( l o g n ) O(log^n) O(logn)的时间复杂度内解决类似于 A ⋅ n x ⋅ x ⋅ x ⋯ x ⏞ A \cdot \begin{matrix} n \\ \overbrace{ x \cdot x \cdot x \cdots x } \end{matrix} A⋅nx⋅x⋅x⋯x 的问题。
4.5.1 斐波那契前 n 项和
首先构造行向量(列向量也可以) F n = [ f n , f n + 1 ] , F n + 1 = [ f n + 1 , f n + 2 ] F_n = [f_n,\ f_{n + 1}], \ F_{n + 1} = [f_{n+1}, \ f_{n + 2}] Fn=[fn, fn+1], Fn+1=[fn+1, fn+2],观察存在矩阵 A A A使得 F n + 1 = F n ⋅ A F_{n+1} = F_n \cdot A Fn+1=Fn⋅A。显然有 [ f n , f n + 1 ] [ 0 1 1 1 ] = [ f n + 1 , f n + 2 ] [f_n, \ f_{n + 1}] \begin{bmatrix} 0 \ &1\\ 1 \ &1 \end{bmatrix}= [f_{n+1}, \ f_{n+2}] [fn, fn+1][0 1 11]=[fn+1, fn+2]因此可得矩阵 A = [ 0 1 1 1 ] A = \begin{bmatrix} 0 \ &1\\ 1 \ &1 \end{bmatrix} A=[0 1 11]那么就有 F n = F 0 ⋅ A n F_n = F_0 \cdot A^n Fn=F0⋅An矩阵乘法没有交换律,但是有结合律,所以可以将 A n A^n An拆开,如 A n = A 1 × A 2 × A 4 × A 8 ⋯ A^n = A^1 \times A^2 \times A^4 \times A^8 \cdots An=A1×A2×A4×A8⋯
而这个题目是求前 f n f_n fn项之和,那么构造向量 F n = [ f n , f n + 1 , S n ] F_n = [f_n, \ f_{n + 1}, \ S_n] Fn=[fn, fn+1, Sn], F n + 1 = [ f n + 1 , f n + 2 , S n + 1 ] F_{n+1} = [f_{n+1}, \ f_{n + 2}, \ S_{n+1}] Fn+1=[fn+1, fn+2, Sn+1],其中 S i S_i Si表示前 i i i项之和。假设存在矩阵 A A A使得 F n ⋅ A = F n + 1 F_n \cdot A = F_{n+1} Fn⋅A=Fn+1,那么可以有 [ f n , f n + 1 , S n ] [ 0 1 0 1 1 1 0 0 1 ] = [ f n + 1 , f n + 2 , S n + 1 ] [f_n, \ f_{n + 1}, \ S_n] \begin{bmatrix} 0 \ &1 \ &0\\ 1 \ &1 \ &1\\ 0 \ &0 \ &1\\ \end{bmatrix}= [f_{n+1}, \ f_{n + 2}, \ S_{n+1}] [fn, fn+1, Sn] 0 1 0 1 1 0 011 =[fn+1, fn+2, Sn+1]因此可以得到矩阵 A = [ 0 1 0 1 1 1 0 0 1 ] A = \begin{bmatrix} 0 \ &1 \ &0\\ 1 \ &1 \ &1\\ 0 \ &0 \ &1\\ \end{bmatrix} A= 0 1 0 1 1 0 011 于是就有 F n = F 0 ⋅ A n = F 1 ⋅ A n − 1 F_n = F_0 \cdot A^n = F_1 \cdot A^{n-1} Fn=F0⋅An=F1⋅An−1。对于 A n − 1 A^{n-1} An−1使用快速幂计算。
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 3;
int n, m;
void mul(int c[], int a[], int b[][N]) {
int temp[N] = {0};
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
temp[i] = (temp[i] + (LL) a[j] * b[j][i]) % m;
memcpy(c, temp, sizeof temp);
}
void mul(int c[][N], int a[][N], int b[][N]) {
int temp[N][N] = {0};
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
for (int k = 0; k < N; k++)
temp[i][j] = (temp[i][j] + (LL) a[i][k] * b[k][j]) % m;
memcpy(c, temp, sizeof temp);
}
int main() {
cin >> n >> m;
int f1[N] = {1, 1, 1};
int a[N][N] = {
{0, 1, 0},
{1, 1, 1},
{0, 0, 1}
};
// 求A^n
n--;
while (n) {
if (n & 1) mul(f1, f1, a); // res = res * a
mul(a, a, a); // a = a * a
n >>= 1;
}
cout << f1[2] << endl;
return 0;
}
4.5.2 佳佳的斐波那契
这个题是上一题的扩展。
算法思路:
由上一题可知 F n = [ f n , f n + 1 , S n ] F_n = [f_n, \ f_{n + 1}, \ S_n] Fn=[fn, fn+1, Sn],这道题给出 T n = 1 ⋅ F 1 + 2 ⋅ F 2 + ⋯ n ⋅ F n T_n = 1 \cdot F_1 + 2 \cdot F_2 + \cdots n \cdot F_n Tn=1⋅F1+2⋅F2+⋯n⋅Fn
注意:要使用 A ⋅ n x ⋅ x ⋅ x ⋯ x ⏞ A \cdot \begin{matrix} n \\ \overbrace{ x \cdot x \cdot x \cdots x } \end{matrix} A⋅nx⋅x⋅x⋯x 解决问题,就必须要保证 A A A中是没有变量的。而 T n = ∑ i = 1 n i ⋅ F i T_n=\sum_{i = 1}^ni\cdot F_i Tn=∑i=1ni⋅Fi 中 F i F_i Fi前面的 i i i是变量,因此要想办法消掉。
做如下变换: n ⋅ S n − T n = ( n − 1 ) F 1 + ( n − 2 ) F 2 + ⋯ F n − 1 ① ( n + 1 ) ⋅ S n + 1 − T n + 1 = n F 1 + ( n − 1 ) F 2 + ⋯ F n ② ② − ① = S n \begin{aligned} n \cdot S_n - T_n &= (n - 1)F_1 + (n-2)F_2 + \cdots F_{n-1} &\text {①}\\ (n+1) \cdot S_{n+1} - T_{n+1}&= nF_1 + (n-1)F_2 + \cdots F_n &\text { ②}\\ ② - ① &= S_n\\ \end{aligned} n⋅Sn−Tn(n+1)⋅Sn+1−Tn+1②−①=(n−1)F1+(n−2)F2+⋯Fn−1=nF1+(n−1)F2+⋯Fn=Sn① ②因此,令 P n = n ⋅ S n − T n P_n = n \cdot S_n - T_n Pn=n⋅Sn−Tn,则 T n = n ⋅ S n − P n P n = P n − 1 + S n − 1 S n = S n − 1 + f n f n = F n − 1 + f n − 2 \begin{aligned} &T_n = n \cdot S_n - P_n\\ &P_n = P_{n-1} + S_{n-1}\\ &S_n = S_{n-1} + f_n\\ &f_n = F_{n-1} + f_{n-2}\\ \end{aligned} Tn=n⋅Sn−PnPn=Pn−1+Sn−1Sn=Sn−1+fnfn=Fn−1+fn−2到此,重新考虑本题,定义 { F n = [ f n , f n + 1 , S n , P n ] F n + 1 = [ f n + 1 , f n + 2 , S n + 1 , P n + 1 ] \begin{cases} &F_n = [f_n, f_{n+1}, S_n, P_n]\\ &F_{n+1} = [f_{n+1}, f_{n+2}, S_{n+1}, P_{n+1}]\\ \end{cases} {Fn=[fn,fn+1,Sn,Pn]Fn+1=[fn+1,fn+2,Sn+1,Pn+1]其中 P n = n ⋅ S n − T n P_n = n \cdot S_n - T_n Pn=n⋅Sn−Tn,则有 [ f n , f n + 1 , S n , P n ] [ 0 1 0 0 1 1 1 0 0 0 1 1 0 0 0 1 ] [ f n + 1 , f n + 2 , S n + 1 , P n + 1 ] [f_n, f_{n+1}, S_n, P_n] \begin{bmatrix} 0 \ &1 \ &0 \ &0\\ 1 \ &1 \ &1 \ &0\\ 0 \ &0 \ &1 \ &1\\ 0 \ &0 \ &0 \ &1\\ \end{bmatrix} [f_{n+1}, f_{n+2}, S_{n+1}, P_{n+1}] [fn,fn+1,Sn,Pn] 0 1 0 0 1 1 0 0 0 1 1 0 0011 [fn+1,fn+2,Sn+1,Pn+1]因此有 A = [ 0 1 0 0 1 1 1 0 0 0 1 1 0 0 0 1 ] A = \begin{bmatrix} 0 \ &1 \ &0 \ &0\\ 1 \ &1 \ &1 \ &0\\ 0 \ &0 \ &1 \ &1\\ 0 \ &0 \ &0 \ &1\\ \end{bmatrix} A= 0 1 0 0 1 1 0 0 0 1 1 0 0011 则 F n = F 1 ⋅ A n − 1 F_n = F_1 \cdot A^{n-1} Fn=F1⋅An−1, F 1 = [ 1 , 1 , 1 , 0 ] F_1 = [1, \ 1, \ 1, \ 0] F1=[1, 1, 1, 0]。
时间复杂度:快速幂 O ( l o g n ) O(log^n) O(logn),矩阵乘法 O ( n 3 ) O(n^3) O(n3),总共时间复杂度为 O ( 4 3 l o g n ) O(4^3log^n) O(43logn)。
Tips:在写代码会遇到 A 1 × 4 × B 4 × 4 A_{1\times4} \times B_{4 \times 4} A1×4×B4×4和 C 4 × 4 × B 4 × 4 C_{4 \times 4} \times B_{4 \times 4} C4×4×B4×4两种情况,为了节省代码,可以将 A 1 × 4 A_{1\times4} A1×4扩充成 A 4 × 4 A_{4\times4} A4×4,其中第二行到第四行的所有值为 0 0 0。比如对于 F 1 = [ 1 , 1 , 1 , 0 ] F_1 = [1, \ 1, \ 1, \ 0] F1=[1, 1, 1, 0]有
F 1 = [ 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 ] F_1 = \begin{bmatrix} 1 \ &1 \ &1 \ &0\\ 0 \ &0 \ &0 \ &0\\ 0 \ &0 \ &0 \ &0\\ 0 \ &0 \ &0 \ &0\\ \end{bmatrix} F1= 1 0 0 0 1 0 0 0 1 0 0 0 0000
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 4;
int n, m;
void mul(int c[][N], int a[][N], int b[][N]) { // c = a * b
// c 和 a 有可能相同,因此定义一个备份数组
// static表示变量只会被定义一次,以后每次访问函数mul它就不需要重新定义
static int t[N][N];
// 这里不能写 sizeof c,传入的c为一个指针,不能通过指针获得数组长度
memset(t, 0, sizeof t);
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
for (int k = 0; k < N; k++)
t[i][j] = (t[i][j] + (LL) a[i][k] * b[k][j]) % m;
memcpy(c, t, sizeof t);
}
int main() {
cin >> n >> m;
// {fn, fn+1, sn, pn}
// pn = n * sn - tn
int f1[N][N] = {1, 1, 1, 0};
int a[N][N] = {
{0, 1, 0, 0},
{1, 1, 1, 0},
{0, 0, 1, 1},
{0, 0, 0, 1},
};
// 快速幂
int k = n - 1;
while (k) {
if (k & 1) mul(f1, f1, a); // f1 = f1 * a
mul(a, a, a); // a = a * a
k >>= 1;
}
cout << (((LL) n * f1[0][2] - f1[0][3]) % m + m) % m << endl; // Tn = n x Sn - Pn
return 0;
}
4.5.3 GT考试
类似于ACwing 1052 设计密码,那一个题 n ∈ [ 1 , 50 ] n \in [1, \ 50] n∈[1, 50],这个题 n ∈ [ 1 , 1 0 9 ] n \in [1, \ 10^9] n∈[1, 109]。本题和设计密码不包含的子串只有一个,如果不包含的子串有多个,则可以参考ACwing 1053 修复DNA。
算法思路:DP状态机
记不吉利数字为 S S S。
集合: f ( i , j ) f(i, \ j) f(i, j)表示所有长度为 i i i、不包含 S S S,且末尾部分与 S S S前缀匹配的最大长度为 j j j的所有字符串的集合。
属性:数量
状态计算:
f
[
i
+
1
]
[
k
]
=
a
[
0
]
[
k
]
∗
f
[
i
]
[
0
]
+
a
[
1
]
[
k
]
∗
f
[
i
]
[
1
]
+
⋯
+
a
[
m
−
1
]
[
k
]
∗
f
[
i
]
[
m
−
1
]
f[i + 1][k] = a[0][k] *f[i][0] + a[1][k] * f[i][1] + \cdots + a[m-1][k] * f[i][m-1]
f[i+1][k]=a[0][k]∗f[i][0]+a[1][k]∗f[i][1]+⋯+a[m−1][k]∗f[i][m−1]
发现 f [ i ] f[i] f[i]与 f [ i + 1 ] f[i+1] f[i+1]的关系是线性的,所以可以用向量来计算, F [ i + 1 ] = F [ i ] ∗ A F[i+1]=F[i]∗A F[i+1]=F[i]∗A。其中 A A A向量中 a [ i ] [ j ] a[i][j] a[i][j]表示在总长度相邻的两个串中,将末尾部分与不吉利数字重复部分的长度从 i i i变成 j j j的方案数,这是一个定值,可以预处理。
比如 f ( i + 1 , 0 ) = a 00 f ( i , 0 ) + a 10 f ( i , 1 ) + ⋯ f ( i + 1 , 1 ) = a 01 f ( i , 1 ) + a 11 f ( i , 1 ) + ⋯ ⋯ \begin{aligned} &f(i+1, \ 0) = a_{00}f(i, \ 0) + a_{10}f(i, \ 1) + \cdots \\ &f(i+1, \ 1) = a_{01}f(i, \ 1) + a_{11}f(i, \ 1) + \cdots \\ & \cdots \end{aligned} f(i+1, 0)=a00f(i, 0)+a10f(i, 1)+⋯f(i+1, 1)=a01f(i, 1)+a11f(i, 1)+⋯⋯那么可以记 F i + 1 = [ f ( i + 1 , 0 ) , f ( i + 1 , 1 ) , ⋯ ] F_{i + 1} = [f(i + 1, \ 0),\ f(i + 1, \ 1), \ \cdots] Fi+1=[f(i+1, 0), f(i+1, 1), ⋯], F i = [ f ( i , 0 ) , f ( i , 1 ) , ⋯ ] F_{i} = [f(i, \ 0),\ f(i, \ 1), \ \cdots] Fi=[f(i, 0), f(i, 1), ⋯],其所有系数记为矩阵 A A A,则有 F i + 1 = F i ⋅ A F_{i + 1} = F_{i} \cdot A Fi+1=Fi⋅A,且 A A A的取值与串的位数 i i i无关,所以可以推出 F n = F n − 1 ⋅ A = F 1 ⋅ A n − 1 F_n = F_{n-1}\cdot A = F_1 \cdot A^{n-1} Fn=Fn−1⋅A=F1⋅An−1。
时间复杂度:
快速幂 O ( l o g n ) O(log^n) O(logn),其中 n ∈ [ 1 , 1 0 9 ] n \in [1, 10^9] n∈[1,109],矩阵乘法 2 0 3 20^3 203,因此总共时间复杂度为 O ( 2 0 3 l o g n ) O(20^3log^n) O(203logn)。
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 25;
int n, m, mod;
char str[N];
int ne[N];
int a[N][N];
void mul(int c[][N], int a[][N], int b[][N]) // c = a * b
{
static int t[N][N];
memset(t, 0, sizeof t);
for (int i = 0; i < m; i++)
for (int j = 0; j < m; j++)
for (int k = 0; k < m; k++)
t[i][j] = (t[i][j] + a[i][k] * b[k][j]) % mod;
memcpy(c, t, sizeof t);
}
int qmi(int k) {
int f0[N][N] = {1};
while (k) {
if (k & 1) mul(f0, f0, a); // f0 = f0 * a
mul(a, a, a); // a = a * a
k >>= 1;
}
int res = 0;
for (int i = 0; i < m; i++) res = (res + f0[0][i]) % mod;
return res;
}
int main() {
cin >> n >> m >> mod;
cin >> (str + 1);
// kmp
for (int i = 2, j = 0; i <= m; i++) {
while (j && str[j + 1] != str[i]) j = ne[j];
if (str[j + 1] == str[i]) j++;
ne[i] = j;
}
// 初始化A[i][j]
for (int j = 0; j < m; j++)
for (int c = '0'; c <= '9'; c++) {
int k = j;
while (k && str[k + 1] != c) k = ne[k];
if (str[k + 1] == c) k++;
if (k < m) a[j][k]++;
}
// F[n] = F[0] * A^n
cout << qmi(n) << endl;
return 0;
}
5 扩展欧几里得算法
裴蜀定理:对于任意一对正整数 a 、 b a、b a、b,那么一定存在非零整数 x 、 y x、y x、y,使得 a x + b y = ( a , b ) ax + by = (a,\space b) ax+by=(a, b)(最大公约数),且 ( a , b ) (a, \space b) (a, b)是 a 、 b a、b a、b能凑出来的最小正整数。
证明:
设 a x + b y = d ax + by = d ax+by=d,因为 a 、 b a、b a、b是 ( a , b ) (a, \space b) (a, b)的倍数,所以 d d d也一定是 ( a , b ) (a, \space b) (a, b)的倍数,那么最小的正整数就应该是 1 1 1倍的情况下,也就是 ( a , b ) (a, \space b) (a, b)。
对于求解 x 、 y x、y x、y就需要使用扩展欧几里得算法。
5.1 扩展欧几里得算法
对于代码中的几点解释:
- 当 b = 0 b=0 b=0的时候, ( a , 0 ) = a (a,\space 0) = a (a, 0)=a,要求一个 x 、 y x、y x、y使得 a x + b y = ( a , 0 ) = a ax + by = (a, 0)= a ax+by=(a,0)=a,显然有 x = 1 , y = 0 x = 1, y = 0 x=1,y=0;
- 当递归函数进行到第12行的时候,就应该存在 b y + ( a m o d b ) x = ( b , a ) = d by + (a \space mod \space b)x = (b, \space a) = d by+(a mod b)x=(b, a)=d因为 a m o d b = a − ⌊ a b ⌋ ⋅ b a \space mod \space b = a - \left \lfloor \frac{a}{b} \right \rfloor·b a mod b=a−⌊ba⌋⋅b,带入上式后整理得 a x + b ( y − ⌊ a b ⌋ ⋅ x ) = d ax + b(y - \left \lfloor \frac{a}{b} \right \rfloor·x)=d ax+b(y−⌊ba⌋⋅x)=d所以就有第12行的代码, x x x不变, y = y − ⌊ a b ⌋ ⋅ x y=y-\left \lfloor \frac{a}{b} \right \rfloor·x y=y−⌊ba⌋⋅x。
#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;
}
5.2 线性同余方程
对等式做变形有:存在一个整数 y y y,使得 a x ≡ b ( m o d m ) = m y + b a x − m y = b a x + m y ′ = b \begin{aligned} ax &\equiv b(mod \space m)\\ &=my + b\\ ax& - my = b\\ ax &+ my'=b\\ \end{aligned} axaxax≡b(mod m)=my+b−my=b+my′=b其中, y ′ = − y y'=-y y′=−y。
原问题就转换为了求一个 x 、 y ′ x、y' x、y′使 a x + m y ′ = b ax + my'=b ax+my′=b有解,且有解的条件为 ( a , m ) ∣ b (a,\space m)|b (a, m)∣b。使用扩展欧几里得算法可以求出 a x + m y ′ = ( a , m ) = d ax + my' = (a, m) = d ax+my′=(a,m)=d,将系数扩大 b / d b / d b/d倍,就可以求出 a x + m y ′ = b ax + my'=b ax+my′=b的解。
#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;
}
5.3 同余方程
算法思路:
a x ≡ 1 ( m o d b ) ⇒ ∃ y ∈ Z , a x + b y = 1 \begin{aligned} &ax \equiv 1 \space(mod \space b)\\ \Rightarrow& \exists\space y\in Z,ax+by = 1\\ \end{aligned} ⇒ax≡1 (mod b)∃ y∈Z,ax+by=1通过扩展欧几里得算法可以求出一组解 ( x 0 , y 0 ) (x_0,y_0) (x0,y0),因为 ( a , b ) = d = 1 (a,b) = d = 1 (a,b)=d=1,故其所有解为 { x = x 0 + k b d = x 0 + k b y = y 0 − k a d = y 0 − k a \begin{cases} x = x_0 + k\frac{b}{d} = x_0 + kb\\ y = y_0 - k\frac{a}{d} = y_0 - ka\\ \end{cases} {x=x0+kdb=x0+kby=y0−kda=y0−ka因为 a x ≡ 1 ( m o d b ) ax \equiv 1 \space(mod \space b) ax≡1 (mod b),因此 x = x 0 + k b ≠ 0 x = x_0 + kb \ne 0 x=x0+kb=0, x x x的最小正整数解就为 x 0 m o d b x_0 \space mod \space b x0 mod b。
#include <cstdio>
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 a, b; scanf("%d%d", &a, &b);
int x, y; exgcd(a, b, x, y);
printf("%d\n", (x % b + b) % b);
return 0;
}
5.4 青蛙约会
算法思路:
假定两只青蛙 A 、 B A、B A、B 初始坐标分别为 a 、 b a、b a、b,不妨设 a < b a < b a<b,若它们跳 x x x 次后相遇。那么 A A A要追 B B B,每跳一次 A A A 追赶 B B B的长度为 ( m − n ) (m-n) (m−n) 米,那么跳了 y y y 圈之后(每一圈长度为 L L L米),相遇时存在如下关系: ( m − n ) ⋅ x = b − a + y ⋅ L ⇒ ( m − n ) ⋅ x − L ⋅ y = b − a ⇒ ( m − n ) ⋅ x + L ⋅ y ′ = b − a \begin{aligned} &(m-n) \cdot x = b - a + y\cdot L\\ \Rightarrow & (m - n) \cdot x - L \cdot y = b - a\\ \Rightarrow & (m - n) \cdot x + L \cdot y' = b - a\\ \end{aligned} ⇒⇒(m−n)⋅x=b−a+y⋅L(m−n)⋅x−L⋅y=b−a(m−n)⋅x+L⋅y′=b−a其中 y ′ = − y y' = -y y′=−y。因为式子中 ( m − n ) 、 L 、 b − a (m -n)、L、b-a (m−n)、L、b−a 均为常数,仅存在变量 x 、 y x、y x、y,所以可以使用扩展欧几里得算法先求解方程 ( m − n ) ⋅ x + L ⋅ y ′ = d (m - n) \cdot x + L \cdot y' = d (m−n)⋅x+L⋅y′=d其中 d = ( m − n , L ) d = (m - n, L) d=(m−n,L)。得到该方程一个解 ( x 0 , y 0 ) (x_0,y_0) (x0,y0),那么也就可以得到该方程的所有解 { x = x 0 − k L d y = y 0 + k m − n d \begin{cases} x = x _0 - k \frac{L}{d}\\ y = y_0 + k \frac{m - n}{d}\\ \end{cases} {x=x0−kdLy=y0+kdm−n因为最终求解最小正整数解,所以 x = x 0 − k L d ≠ 0 x = x _0 - k \frac{L}{d} \ne 0 x=x0−kdL=0,这里和上面一题最后一样, x = x 0 m o d L d x = x_0 \space mod \space \frac{L}{d} x=x0 mod dL ,最后将 ( x , y ) (x,y) (x,y)扩大 b − a d \frac{b-a}{d} db−a倍即可。
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long LL;
LL exgcd(LL a, LL b, LL &x, LL &y) {
if (!b) {
x = 1, y = 0;
return a;
}
LL d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
int main() {
LL a, b, m, n, L;
scanf("%lld%lld%lld%lld%lld", &a, &b, &m, &n, &L);
LL x, y;
LL d = exgcd(m - n, L, x, y);
if ((b - a) % d) puts("Impossible");
else {
x *= (b - a) / d;
LL t = abs(L / d);
printf("%lld\n", (x % t + t) % t);
}
return 0;
}
5.5 最幸运的数字
算法思路:
假设有 x x x个 8 8 8,则有 x 8 ⋯ 8 ⏞ ⇒ 8 × x 1 ⋯ 1 ⏞ ⇒ 8 × x 9 ⋯ 9 ⏞ 9 ⇒ 8 × 1 0 x − 1 9 \begin{aligned} &\begin{matrix} x \\ \overbrace{ 8 \cdots 8 } \end{matrix} \Rightarrow 8 \times \begin{matrix} x \\ \overbrace{ 1 \cdots 1 } \end{matrix} \Rightarrow 8 \times \frac{\begin{matrix} x \\ \overbrace{ 9 \cdots 9 } \end{matrix}}{9} \Rightarrow 8\times \frac{10^x - 1}{9} \end{aligned} x8⋯8 ⇒8×x1⋯1 ⇒8×9x9⋯9 ⇒8×910x−1题目中问是否存在 x x x 个 8 8 8 能整除 L L L,那么就转换为 L ∣ 8 1 0 x − 1 9 ⇒ 9 L ∣ 8 ( 1 0 x − 1 ) L | 8 \frac{10^x - 1}{9} \Rightarrow 9L | 8 (10^x - 1) L∣8910x−1⇒9L∣8(10x−1)令 d = ( L , 8 ) d = (L, 8) d=(L,8) 后有 9 L d ∣ 1 0 x − 1 \frac{9L}{d} | 10^x - 1 d9L∣10x−1
解释一下这一步:记 A = 1 0 x − 1 A = 10^x - 1 A=10x−1,则原式变成 9 L ∣ 8 A 9L|8A 9L∣8A因为 g c d ( 8 , 9 ) = 1 gcd(8, 9) = 1 gcd(8,9)=1,令 g c d ( 8 , 9 L ) = g c d ( 8 , L ) = d gcd(8, 9L)=gcd(8, L)=d gcd(8,9L)=gcd(8,L)=d,于是有 9 L d ∣ 8 d ⋅ A \frac{9L}{d}|\frac{8}{d} \cdot A d9L∣d8⋅A又因为 g c d ( 9 L d , 8 d ) = g c d ( L d , 8 d ) = 1 gcd(\frac{9L}{d}, \frac{8}{d}) = gcd(\frac{L}{d}, \frac{8}{d}) = 1 gcd(d9L,d8)=gcd(dL,d8)=1故 9 L d ∣ A \frac{9L}{d}|A d9L∣A
因为 9 L d \frac{9L}{d} d9L 为一个常数,令 C = 9 L d C=\frac{9L}{d} C=d9L,则有 C ∣ 1 0 x − 1 ⇒ 1 0 x ≡ 1 ( m o d C ) C|10^x - 1 \\ \Rightarrow 10^x \equiv 1 (mod \space C) C∣10x−1⇒10x≡1(mod C)
欧拉定理推论(费马小定理):若 a a a与 p p p互质且 p p p为质数的时候
( a ϕ ( p ) = a p − 1 ) ≡ 1 ( m o d p ) (a^{\phi(p)} = a^{p - 1}) \equiv 1(mod \space p) (aϕ(p)=ap−1)≡1(mod p)
这个题要求解最小的 x x x,先给出结论:满足方程 1 0 x ≡ 1 ( m o d C ) 10^x \equiv 1 (mod \space C) 10x≡1(mod C) 最小的正整数 x x x 一定满足 x ∣ ϕ ( C ) x | \phi(C) x∣ϕ(C)。
结论证明:
假设 x x x 是满足方程 1 0 x ≡ 1 ( m o d C ) 10^x \equiv 1 (mod \space C) 10x≡1(mod C) 最小的正整数,但是 x x x 并不能整除 ϕ ( C ) \phi(C) ϕ(C),那么必然满足 ϕ ( C ) = q ⋅ x + r \phi(C) = q \cdot x + r ϕ(C)=q⋅x+r,其中 0 < r < x 0 < r < x 0<r<x。因为由 1 0 x ≡ 1 ⟺ 1 0 q x ≡ 1 10^x \equiv 1 \Longleftrightarrow 10^{qx} \equiv 1 10x≡1⟺10qx≡1, 1 0 ϕ ( C ) ≡ 1 ⟺ 1 0 q ⋅ x + r ≡ 1 10^{\phi(C)} \equiv 1 \Longleftrightarrow 10^{q \cdot x + r} \equiv 1 10ϕ(C)≡1⟺10q⋅x+r≡1,那么可以得到 1 0 r ≡ 1 10^r \equiv 1 10r≡1 且 0 < r < x 0 < r < x 0<r<x,这里的找到了一个更小的正整数满足方程 1 0 x ≡ 1 ( m o d C ) 10^x \equiv 1 (mod \space C) 10x≡1(mod C),与假设矛盾。
时间复杂度: O ( n ) O(\sqrt{n}) O(n),主要是求约数。
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
LL qmul(LL a, LL k, LL b) {
LL res = 0;
while (k) {
if (k & 1) res = (res + a) % b;
a = (a + a) % b;
k >>= 1;
}
return res;
}
// 因为 max(L) = 2e9,C = 9 * L / d,C 会达到 10e10 级别
// 在qmi中,如果再平方,那么达到 10e20 会爆 long long
// 因此要使用 龟速乘qmul 来代替乘法
LL qmi(LL a, LL k, LL b) {
LL res = 1;
while (k) {
if (k & 1) res = qmul(res, a, b);
a = qmul(a, a, b);
k >>= 1;
}
return res;
}
LL get_euler(LL C) {
LL res = C;
for (LL i = 2; i <= C / i; i++)
if (C % i == 0) {
while (C % i == 0) C /= i;
res = res / i * (i - 1); // 先除后乘,防止溢出
}
if (C > 1) res = res / C * (C - 1);
return res;
}
int main() {
int T = 1; // case
LL L;
while (scanf("%lld", &L), L) {
int d = 1;
while (L % (d * 2) == 0 && d * 2 <= 8) d *= 2; // d = gcd(L, 8)
LL C = 9 * L / d;
LL phi = get_euler(C);
LL res = 1e18;
if (C % 2 == 0 || C % 5 == 0) res = 0; // 10 和 C 不互质无解(费马小定理)
else {
for (LL d = 1; d * d <= phi; d++) // 枚举 phi 的所有约数 d 和 phi / d
if (phi % d == 0) {
if (qmi(10, d, C) == 1) res = min(res, d);
if (qmi(10, phi / d, C) == 1) res = min(res, phi / d);
}
}
printf("Case %d: %lld\n", T++, res);
}
return 0;
}
如果不适用龟速乘,可以看编译器是否支持__int128
,如果支持,可以省去龟速乘代码,直接在快速幂中修改,如下所示:
LL qmi(LL a, LL k, LL b) {
LL res = 1;
while (k) {
if (k & 1) res = (__int128)res * a % b;
a = (__int128) a * a % b;
k >>= 1;
}
return res;
}