數學芝士

最近開始補數學的鍋了。
此次確實寫完了。
大概提升組已經勾勒,NOIP前數論部分應該不會再多學了。c++

update on 9.29:忽然知道12月才NOIP,不得不學一些省選知識了。

右鍵數學公式→Math SettingsMath RendererCommonHTML以得到更佳體驗。
Markdown數學公式
Markdowngit

數論部分

質數及相關內容

簡介

質數就是隻能被1和它自己整除的數,它的個數大概爲\(n\div \ln n\)個。
一些基本性質好比任何數都與1互質,除了2其餘的偶數都不是質數等等,比較簡單ide

Eratosthenes篩素數

利用了一個質數的倍數必定不是質數的原理。
複雜度\(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

\[N = p_1^{c^1}P_2^{c^2} ... p_n^{c^n} \]

質因數分解

通常採用試除法。
就是對\(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 = p_1^{c^1}P_2^{c^2} ... p_n^{c^n} \]

那麼N的正約數集合能夠表示爲:

\[\{ p_1^{b_1}p_2^{b_2} ... p_n^{b_n} \},其中0 <= b_i <= c_i \]

N的正約數個數爲:

\[(c_1 + 1) * (c_2 + 1) * ... * (c_n + 1) \]

N的全部的正約數的和爲:

\[(1 + p_1 + p_1^2 + ... + p_1^{c_1})*...*(1 + p_n + p_n^2 + p_n^{c_n}) \]

證實應該比較簡單。

GCD(最大公約數)

展轉相除法

直接上代碼:

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\))

  • gcd(a, b) = gcd(b, a-b) = gcd(a, a - b)
  • gcd(2a, 2b) = 2 * gcd(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,有:

\[gcd(a,b) * 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\),那麼得:

\[lcm(a, b) = lcm(a_0 * d, b_0 * d) = lcm(a_0, b_0) * d = a_0 * b_0 * d = a * b / d \]

由於 \(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 的質因子,則:

\[\varphi(N) = N * \frac{p_1-1}{p_1} * \frac{p_2-1}{p_2} * ... * \frac{p_n-1}{p_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;
}

Eratosthenes篩求歐拉函數

複雜度\(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 互質,則有:

\[a^b \equiv a^{b mod \varphi(n)} (mod 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 使其知足:

\[bx + (a - b[a/b])y = ay + b(x - [a/b]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;

盧卡斯定理(\(Lucas\)

定理內容

普通的求組合數通常提早預處理出來階乘的逆元。
可是當數據範圍巨大的時候咱們沒法進行預處理,或者說給的mod很爛的時候,這時候咱們就要用到盧卡斯定理來解決這個問題。

公式: $Lucas(n, m, mod) = $ \(C(n\) % mod\(, m\) % \(mod)+Lucas(n/mod, m/mod, mod)\)
其中\(Lucas(n, 0, mod) = 1\)
複雜度 \(O(p+log⁡p)∼O(log⁡n)\),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;
}

恐怖證實

暫無,證實有億點點難。

中國剩餘定理(CRT)

基本內容

中國剩餘定理主要用於求解模數互質的一組線性同餘方程的解。

\(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\),方程組

\[\begin{cases} x\equiv a_1(modm_1) \\ x\equiv a_2(mod m_2)\\...\\x \equiv a_n(mod m_n) \end{cases} \]

有整數解,解爲\(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)\),因此證實得出來的解是正確的。

相關文章
相關標籤/搜索