数学芝士
最近开始补数学的锅了。
这次确实写完了。
大概提高组已经勾勒,NOIP前数论部分应该不会再多学了。
update on 11.1:
CSP前,想复习一下基础数论知识,发现自己原来写的出了一堆错,所有的筛法都是从2开始筛, 大家自己注意一下。
update on 9.29:突然知道12月才NOIP,不得不学一些省选知识了。
右键数学公式→Math Settings→Math Renderer→CommonHTML以获得更佳体验。
Markdown数学公式
Markdown
数论部分
质数及相关内容
简介
质数就是只能被1和它本身整除的数,它的个数大概为
Eratosthenes筛素数
利用了一个质数的倍数一定不是质数的原理。
复杂度
int prime[N], tot, vis[N]; //vis[i]表示i不是质数
void shai() {
for(register int i = 2; i <= n; ++i) {
if(vis[i]) continue;
prime[++tot] = i;
for(register int j = 2; j * i <= n; ++j) {
vis[i*j] = 1;
}
}
}
事实上对于每个质数,我们只需要从
int prime[N], tot, vis[N]; //vis[i]表示i不是质数
void shai() {
for(register int i = 2; i <= n; ++i) {
if(vis[i]) continue;
prime[++tot] = i;
for(register int j = i; j * i <= n; ++j) {//从i×i开始
vis[i*j] = 1;
}
}
}
线性筛素数
进一步优化Eratosthenes筛素数,使复杂度降为
大概原理就是用每个合数的最小质因子筛掉它。
int prime[N], tot, vis[N]; //vis[i]中存i的最小质因子
void shai() {
for(register int i = 2; i <= n; ++i) {
if(vis[i] == 0) { vis[i] = i, prime[++tot] = i; }
for(register int j = 1; j <= tot; ++j) {
if(prime[j] > vis[i] || prime[j]*i > n) break;//注意是prime[j] > vis[i]
vis[prime[j] * i] = prime[j];
}
}
}
线性筛应该是比赛中用的最多的。
算术基本定理
任何一个大于1的整数都能唯一分解成有限个质数的乘积。
质因数分解
一般采用试除法。
就是对
int p[N], c[N], tot; //p[i]中存质因子,c[i]存个数。
void divide() {
for(register int i = 2; i <= sqrt(n + 0.5); ++i) {
if(n % i == 0) {
p[++tot] = i;
while(n % i == 0) {
n /= i, c[tot]++;
}
}
}
if(n > 1) p[++tot] = n, c[tot] = 1;//注意别忘了。
}
约数及相关内容
基本内容
若整数n除以整数d的余数为0,那么就说d能整除n,即
算术基本定理的推论
正整数N被唯一分解为下面的式子:
那么N的正约数集合可以表示为:
N的正约数个数为:
N的所有的正约数的和为:
证明应该比较简单。
GCD(最大公约数)
辗转相除法
直接上代码:
inline int gcd(int x, int y) {
return y == 0 ? x : gcd(y, x%y);
}
复杂度:
证明
若
若
对于a,b, 的任意公约数d,显然的
由此得证。
更相减损术
对于正整数 a, b,有如下结论:(设
- gcd(a, b) = gcd(b, a-b) = gcd(a, a - b)
- gcd(2a, 2b) = 2 * gcd(a, b)
可以由此求得最大公约数。
复杂度基本保持在
它的主要用处是做高精gcd。
inline int get(int x, int y) {
if(x == y) return x;
if(x%2 == 0 && y%2 == 0) return 2 * get(x/2, y/2);
if(x < y) swap(x, y);
return get(y, x - y);
}
建议用while循环,否则炸栈内存。
多个数的最大公约数
可以开一个队列,每次取出两个数做gcd并把他们的gcd放入队列,直到队列中只剩一个数,那个数就是最大公约数。
最小公倍数
一般写为
定理
设两个正整数a,b,有:
证明
设
因为
倍数法筛约数
就是考虑每个约数d的贡献。
这个方法的复杂度是
vector<int> factor[N];
void shai() {
for(int i = 1; i <= n; ++i)
for(int j = 1; j * i <= n; ++j)
factor[i*j].push_back(i);
}
线性筛求约数个数
原理是线性筛质数和算数基本定理的结合。
int d[N], vis[N], num[N], prime[N], tot;//d[i]存i的u约数个数,num[i]存i的最小质因子的个数
void shai() {
d[1] = 1;
for(register int i = 2; i <= n; ++i) {
//质数的约数个数为2,最小质因子的个数为1。
if(vis[i] == 0) prime[++tot] = i, d[i] = 2, num[i] = 1;
for(register int j = 1; j <= tot && i*prime[j] <= n; ++j) {
vis[prime[j]*i] = 1;//最小质因子为prime[j]的合数
if(i % prime[j] == 0) {//i的最小质因子为prime[j]
num[i*prime[j]] = num[i] + 1;
d[i*prime[j]] = d[i] / (num[i*prime[j]]) * (num[i*prime[j]] + 1);
//相当于去掉原来的prime[j],加入新的prime[j]。
break;
}
else {
num[i*prime[j]] = 1;
d[i*prime[j]] = d[i]*2;//这个自己分析吧。
}
}
}
}
复杂度不用过多分析,就是一个线性筛。
至于线性求约数和,暂时不讲。
过渡段
前面都是必须学会的基础知识,为后面做准备,后面的难度应该会更难一些。
欧拉函数
互质
对于两个正整数a,b,如果
对于多个数同样适用。
基本内容
其中
复杂度
这就是
代码时刻
以洛谷板子题为例。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline int read(int s = 0, char ch = getchar()) {
while(!isdigit(ch)) { ch = getchar(); }
while(isdigit(ch)) { s = s*10 + ch - '0'; ch = getchar(); }
return s;
}
ll jie[100005], n, m, mod;
ll qpow(ll a, ll b, ll ans = 1) {//快速幂求逆元
while(b) {
if(b & 1) ans = ans * a % mod;
b >>= 1, a = a*a % mod;
}
return ans;
}
ll suan(ll a, ll b) {//组合数公式
if(a > b) return 0;
return (jie[b] * qpow(jie[a], mod-2) % mod * qpow(jie[b-a], mod-2) % mod);
}
ll Lucas(int a, int b) {//递归实现Lucas
if(!a) return 1;
return suan(a%mod, b%mod) * Lucas(a/mod, b/mod) % mod;
}
int main() {
int T = read();
while(T--) {
n = read(), m = read(); mod = read();
jie[0] = 1;
for(register int i = 1; i <= mod; ++i) jie[i] = jie[i-1] * i % mod;
cout << Lucas(n, n + m) << '\n';
}
return 0;
}
恐怖证明
暂无,证明有亿点点难。
中国剩余定理(CRT)
基本内容
中国剩余定理主要用于求解模数互质的一组线性同余方程的解。
设
对于任意的n个整数
有整数解,解为
注意求出来的只是一组特解, 方程的通解可以表示为
复杂度:取决与逆元的求解。中国剩余定理为
代码时刻
洛谷板子题
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 20;
int n, a[N], m[N], Mi[N], M = 1, ans;
int exgcd(int a, int b, int &x, int &y) {
if(b == 0) { return x = 1, y = 0, a; }
int gcd = exgcd(b, a%b, x, y), c = x;
x = y, y = c - (a/b)*y;
return gcd;
}
signed main() {
cin >> n;
for(register int i = 1; i <= n; ++i) {
cin >> m[i] >> a[i];
M *= m[i];
}
for(register int i = 1; i <= n; ++i) {
Mi[i] = M / m[i];
}
for(register int i = 1; i <= n; ++i) {
int x, y;
int gcd = exgcd(Mi[i], m[i], x, y);
ans += a[i] * Mi[i] * (x < 0 ? x%m[i] + m[i] : x);
}
cout << ans%M << '\n';
return 0;
}
感觉比较清晰。
基本证明
因为