最近开始补数学的锅了。
此次确实写完了。
大概提升组已经勾勒,NOIP前数论部分应该不会再多学了。c++
右键数学公式→Math Settings
→Math Renderer
→CommonHTML
以得到更佳体验。
Markdown数学公式
Markdowngit
质数就是只能被1和它自己整除的数,它的个数大概为\(n\div \ln n\)个。
一些基本性质好比任何数都与1互质,除了2其余的偶数都不是质数等等,比较简单ide
利用了一个质数的倍数必定不是质数的原理。
复杂度\(O(nlognlogn)\)。函数
int prime[N], tot, vis[N]; //vis[i]表示i不是质数 void shai() { for(register int i = 1; i <= n; ++i) { if(vis[i]) continue; prime[++tot] = i; for(register int j = 2; j * i <= n; ++j) { vis[i*j] = 1; } } }
事实上对于每一个质数,咱们只须要从\(x^2\)开始就能够。优化
int prime[N], tot, vis[N]; //vis[i]表示i不是质数 void shai() { for(register int i = 1; 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筛素数,使复杂度降为\(O(n)\)。
大概原理就是用每一个合数的最小质因子筛掉它。ui
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]; } } }
线性筛应该是比赛中用的最多的。spa
任何一个大于1的整数都能惟一分解成有限个质数的乘积。.net
通常采用试除法。
就是对\(1 - \sqrt n\) 中的每一个能整除N的数,把它所有除尽。code
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,即\(d|n\)。blog
正整数N被惟一分解为下面的式子:
那么N的正约数集合能够表示为:
N的正约数个数为:
N的全部的正约数的和为:
证实应该比较简单。
直接上代码:
inline int gcd(int x, int y) { return y == 0 ? x : gcd(y, x%y); }
复杂度:\(O(logn)\)
若 \(a < b\) ,显然的\(gcd(a, b) = gcd(b, a) = gcd(b, a\) % \(b)\)
若 \(a > b\) ,能够设 $a = q * b + r \(,显然\)r = a$ % \(b\)
对于a,b, 的任意公约数d,显然的 \(d | a, d|q*b\),那么显然\(d|a-q*b\),也就是\(d|r\)。
由此得证。
对于正整数 a, b,有以下结论:(设 \(a > b\))
能够由此求得最大公约数。
复杂度基本保持在\(O(logn)\),不过有几率退化成\(O(n)\)。
它的主要用处是作高精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放入队列,直到队列中只剩一个数,那个数就是最大公约数。
通常写为\(lcm(a,b)\)。
设两个正整数a,b,有:
设\(d = gcd(a, b),a_0 = a/d, b_0 = b/d\),显然\(gcd(a_0, b_0) = 1, lcm(a_0, b_0) = a_0 * b_0\),那么得:
由于 \(d = gcd(a, b)\),因此证实完成。
就是考虑每一个约数d的贡献。
这个方法的复杂度是 \(O(nlogn)\),可是优势是能够求出全部的具体的约数且代码好写。
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,若是\(gcd(a,b) = 1\),咱们说这两个数互质。
对于多个数一样适用。
\(1-N\) 中与 \(N\) 互质的数的个数叫作欧拉函数,写为 \(\varphi(N)\)。
设\(p_1,p_2,p_3, ... ,p_n\) 为N 的质因子,则:
证实:方法不少,能够用容斥原理证实,此处略过。
因此咱们只要分解质因数就能够求出欧拉函数的值。
int phi(int n) { int ans = n; for(register int i = 2; i <= sqrt(n + 0.5); ++i) { if(n % i == 0) ans = ans / i * (i-1); while(n % i == 0) n /= i; } if(n > 1) ans = ans / n * (n-1); return ans; }
复杂度\(O(nlogn)\),代码具短。
大体思想就是用每一个质数去筛欧拉函数。
void shai() { for(register int i = 1; i <= n; ++i) phi[i] = i; for(register int i = 1; i <= n; ++i) { if(phi[i] == i) {//i为质数 for(register int j = i; j <= n; j += i) phi[j] = phi[j] / i * (i-1); } } }
利用线性筛计算欧拉函数,具体原理代码中讲解。
int phi[N], prime[N], tot, vis[N]; void shai() { phi[1] = 1;//1的欧拉函数是1 for(register int i = 2; i <= n; ++i) { if(vis[i] == 0) { prime[++tot] = i, phi[i] = i-1;//质数的欧拉函数是i-1 } for(register int j = 1; j <= tot && i * prime[j] <= n; ++j) { vis[i * prime[j]] = 1; if(i % prime[j] == 0) { //若是prime[j]是i的一个质因子 //f[i*prime[j]] = prime[j] * f[i],由于i中包括(i×prime[j])的全部质因子。 phi[prime[j] * i] = prime[j] * phi[i]; break; } else {//prime[j]与i互质 //根据它是积性函数:f(ab) = f(a) * f(b), [gcd(a, b) == 1] phi[prime[j] * i] = (prime[j] - 1) * phi[i]; } } } }
若是 a,b 互质,而且有 \(f(ab) = f(a) * f(b)\) ,那么就说函数为积性函数。
暂时没有遇到过与他相关的题目。
若整数a,b除以正整数m的余数相等,则称 a,b 模 m 同余,记为 \(a \equiv b(mod\) \(m)\) 。
对于任意正整数 \(a[0 <= a <= m-1]\),集合 \(\{ a + km \}(k = 0, 1, 2, 3 ...)\) 模 m 同余,余数为 a ,则称该集合为一个模 m 的同余类。
显然,m 的同余类一共有m个,它们构成 m 的彻底剩余系。
若 p 是质数,则对于任意整数 a, 有 \(a^p \equiv a(mod\) \(p)\)。
也能够写作 $ a^{p-1} \equiv 1(mod$ \(p)\)。
我的感受证实就算了吧。
若正整数 a, n 互质,则有 $ a^{\varphi(n)} \equiv a(mod$ \(p)\),其中 $ \varphi(n) $ 是欧拉函数。
推论 : 若正整数 a,n 互质,则有:
常常用于须要取模的乘方。
证实略。
若 \(p\) 为质数且\(x∈(0,p)\),则方程 \(x^2≡1(mod\) \(p)\) 的解为 \(x = 1\), $ x = p−1$。
移项后平方差展开后由于 \(0 <= x <= p\),因此能够证实。
方程 \(ax + by = c [a为正整数, b为正整数]\),有解的充要条件是 \(gcd(a, b) | c\)。
注意其中 \(c\) 必须大于等于 \(gcd(a, b)\)。
放个板子题
#include <cstdio> using namespace std; inline int gcd(int x, int y) { return y ? gcd(y, x % y) : x; } int main() { int n, ans = 0, tmp = 0; scanf("%d", &n); for(int i = 1; i <= n; i++) { scanf("%d", &tmp); if(tmp < 0) tmp = -tmp; ans = gcd(ans, tmp); } printf("%d", ans); return 0; }
求解裴蜀定理一组解的东西。
经过求最大公约数的过程当中求出它的一组特解。
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), d = x; x = y; y = d - y * a / b; return gcd; }
在求最大公约数的最后一步,由于此时 \(b = 0, a\) 就是最大公约数,因此显然的 \(x = 1, y = 0\)。
由于 \(gcd(a, b) = gcd(b, a\) % \(b)\),因此设x, y 使其知足:
因此 \(x = y, y = x - a/b * y\)。
设求出的特解为 \(x_0, y_0\)。
通解为 \(x = x_0 + k * b/gcd\), $ y = y_0 - k * a/gcd $,k 为整数。
众所周知同余不知足同除性,因此数学家们弄出了逆元这个东西。
通俗的来讲就是若是整数 \(b, m\) 互质,若是有 $ b * x \equiv 1(mod$ $m) $,那么 \(x\) 就是 \(b\) 在 \(mod\) \(m\) 下的乘法逆元。
求法:
比较简单的是费马小定理逆元,可是要求模数为质数。
还有欧拉定理求逆元和扩展欧几里得求逆元。
建议直接背过:
Inv[ 1 ] = 1; for( int i = 2; i <= n; i++ ) Inv[ i ] = ( p - p / i ) * Inv[ p % i ] % p;
普通的求组合数通常提早预处理出来阶乘的逆元。
可是当数据范围巨大的时候咱们没法进行预处理,或者说给的mod很烂的时候,这时候咱们就要用到卢卡斯定理来解决这个问题。
公式: $Lucas(n, m, mod) = $ \(C(n\) % mod\(, m\) % \(mod)+Lucas(n/mod, m/mod, mod)\)
其中\(Lucas(n, 0, mod) = 1\)
复杂度 \(O(p+logp)∼O(logn)\),p为质数。
这就是\(Lucas\)定理的基本内容,证实放在代码后。
以洛谷板子题为例。
#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; }
暂无,证实有亿点点难。
中国剩余定理主要用于求解模数互质的一组线性同余方程的解。
设\(m_1, m_2, m_3, .... , m_n\)为两两互质的整数,\(M = \prod \frac{n}{i=1}mi\),\(Mi = M / m_i\),\(t_i\)是线性同余方程 \(M_it_i \equiv 1(\)mod \(m_i)\) 的一个解。
对于任意的n个整数\(a_1, a_2, a_3, ... , a_n\),方程组
有整数解,解为\(x = \sum_{i=1}^{n}a_iM_it_i\)
注意求出来的只是一组特解, 方程的通解能够表示为\(x + kM(k为整数)\)。
复杂度:取决与逆元的求解。中国剩余定理为\(O(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; }
感受比较清晰。
由于\(M_i = M/m_i\),因此对于任意的\(k(k != i)\),都有\(a_iM_it_i\equiv 0(\)mod \(m_k)\),而且有\(a_iM_it_i \equiv a_i(\)mod \(m_i)\),因此证实得出来的解是正确的。