感谢@Cute_SilverWolf为这篇文章作出的贡献。
写这篇文章是因为这四节课题目看似很少,内容是真多。在几个月前我就复习过一次这俩东西但是没写笔记现在又忘完了,于是趁自己有时间复习提高组就赶紧写了。
数论部分
整除
对于两个整数a,b,存在两个唯一的整数q,r满足:
b=aq+r(0≤r≤∣a∣)
当r=0,我们就说a整除b,记作a∣b。
整除的基本性质
-
a∣c,b∣c,(a,b)=1⇒ab∣c
-
a∣bc,(a,b)=1⇒a∣c
-
p∣ab⇒p∣a或p∣b
公约数公倍数
-
(a,b)=(a,a+b)=(a,a−b)=(a,amodb)
-
(ac,bc)=c(a,b)
-
(a,b)×[a,b]=ab
神秘结论
欧几里得算法 gcd
前面我们知道,(a,b)=(a,amodb),所以可以写出下面的代码求解gcd:
1 2 3 4
| int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); }
|
裴蜀定理
设a,b不全为0。
∃x,y使得ax+by=gcd(a,b)
不失一般性,设0<a≤b。
下面对a的两种情况进行讨论:
-
a=0,令x=0,y=1满足条件。
-
a>0,则令c=bmoda=b−ak。
所以我们可以尝试找到x0,y0满足cx0+ay0=gcd(c,a)。
所以:
gcd(a,b)=gcd(c,a)=(b−ak)x0+ay0=(y0−x0k)a+x0b
所以原方程的x=y0−x0k,y=x0。
扩展欧几里得算法 exgcd
就是利用到我们刚刚提到的裴蜀定理:
1 2 3 4 5 6 7 8 9 10 11
| int exgcd(int a, int b, int &x, int &y) { if (b == 0) { x = 1; y = 0; return a; } int d = exgcd(b, a % b, y, x); y -= (a / b) * x; return d; }
|
代码源OJ 488 - 扩展欧几里得2
题目描述
多测,给你三个整数a,b,d,输出ax+by=d的最小非负整数解(x,y)。如果无解输出−1,否则输出x最小的解。
对于100%的数据,1≤T≤104,1≤a,b≤109,1≤d≤1018。
思路
令g为gcd(a,b),且x0,y0为ax0+by0=g的一组解。
首先如果d无法整除g,原方程无解。
否则x=x0×(gd),y=y0×(gd)就是原方程的一组解。我们可以求出最小的x之后再算出y,具体实现看代码。
注意会爆longlong。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| #include<bits/stdc++.h> using namespace std; #define int long long int exgcd(int a, int b, __int128 &x, __int128 &y) { if (b == 0) { x = 1; y = 0; return a; } int d = exgcd(b, a % b, y, x); y -= (a / b) * x; return d; } void solve() { int a, b, d; cin >> a >> b >> d; __int128 x, y; __int128 g = exgcd(a, b, x, y); if (d % g) { cout << -1 << '\n'; return; } x *= (d / g); x %= (b / g); if (x < 0) { x += (b / g); } y = (__int128(d / g) - __int128(a / g) * x) / __int128(b / g); if (y < 0) { cout << -1 << '\n'; return; } cout << (int)x << ' ' << (int)y << '\n'; } signed main() { int T; cin >> T; while (T--) { solve(); } return 0; }
|
同余
-
a≡b(modn),a≡b(modm)⇒a≡b(mod[n,m])
-
a≡b(modm)⇒ka≡kb(modm)
-
(k,m)=d,ka≡kb(modm)⇒a≡b(moddm)
线性同余方程
给你一个方程:
ax≡b(modm)
我们就可以转化为:
ax−my=b
还有一种apiad在视频里提到了的方法,我也写下来:
我们肯定有:
mx≡0(modm)
所以我们就能得到:
(m−a)x≡−b(modm)
辗转相除即可。
简化剩余系和欧拉函数
懒得写前面一堆奇怪的剩余系得定义了。
所有的整数n满足0<n≤m且(n,m)=1,构成了模m的简化剩余系。
记这样n的个数为ϕ(m),也就是大名鼎鼎的欧拉函数。
ϕ(m)=m×Πp∣mpp−1
所以我们有下面一个求欧拉函数的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| for (int i = 2; i * i <= n; i++) { if (n % i == 0) { phi /= i; phi *= (i - 1); while (n % i == 0) { n /= i; } } } if (n != 1) { phi /= n; phi *= (n - 1); }
|
我们又发现这是个积性函数,所以线性筛代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| vector<int> phisieve(int n) { vector<int> primes, phi(n + 1); vector<bool> vis(n + 1, 0); phi[1] = 1, phi[0] = 0; vis[0] = vis[1] = 1; for (int i = 2; i <= n; i++) { if (!vis[i]) { phi[i] = i - 1; primes.push_back(i); } for (int j = 0; j < primes.size() && i * primes[j] <= n; j++) { vis[i * primes[j]] = 1; if (i % primes[j]) { phi[i * primes[j]] = phi[i] * (primes[j] - 1); } else { phi[i * primes[j]] = phi[i] * primes[j]; break; } } } return phi; }
|
两个结论:
d∣n∑ϕ(d)=n
(a,n)=1⇒aϕ(n)≡1(modn)
你会发现,第二个其实是就是费马小定理的加强版。
P2398 GCD SUM
题目描述
给你一个n,求:
i=1∑nj=1∑ngcd(i,j)
1≤n≤105。
思路
原式:
=i=1∑nj=1∑nd∣gcd(i,j)∑ϕ(d)
=i=1∑nj=1∑nd∣i,d∣j∑ϕ(d)
=d=1∑nϕ(d)×⌊dn⌋×⌊dn⌋
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| #include<bits/stdc++.h> using namespace std; #define int long long vector<int> phisieve(int n) { vector<int> primes, phi(n + 1); vector<bool> vis(n + 1, 0); phi[1] = 1, phi[0] = 0; vis[0] = vis[1] = 1; for (int i = 2; i <= n; i++) { if (!vis[i]) { phi[i] = i - 1; primes.push_back(i); } for (int j = 0; j < primes.size() && i * primes[j] <= n; j++) { vis[i * primes[j]] = 1; if (i % primes[j]) { phi[i * primes[j]] = phi[i] * (primes[j] - 1); } else { phi[i * primes[j]] = phi[i] * primes[j]; break; } } } return phi; } signed main() { int n; cin >> n; auto phi = phisieve(n + 5); int ans = 0; for (int i = 1; i <= n; i++) { ans += phi[i] * (n / i) * (n / i); } cout << ans; return 0; }
|
逆元
威尔逊定理
组合部分