數學
Outline
- 快速冪
- 數論
- 矩陣
- 組合
快速冪
- 求 \(a^b \pmod{10^9+7}\)
- \(0\leq a,b\leq 10^9\)
- 求 \(a^b \pmod{10^9+7}\)
- 暴力枚舉?
- \(O(b)\) TLE
- \(a^7=a^4 \cdot a^2 \cdot a^1\)
- 按照二進位分解
int qpow(int a,int b,int mod){
int ret=1;
while(b>0){
if(b&1) ret=ret*a%mod;
a=a*a%mod;
b>>=1;
}
return ret;
}數論
判別質數
- 質數: 只有 \(1,x\) 兩個因數
- 可以枚舉 \(2 \sim \sqrt(x)\) 是否可被 \(x\) 整除
- \(O(\sqrt{N})\)
bool isprime(int x){
if(x==1) return false;
for(int i=2;i<=sqrt(x);i++){
if(x%i==0) return false;
}
return true;
}質數篩法
- 如果要找 \(1 \sim N\) 的質數, \(O(N\cdot \sqrt{N})\) 太慢
- 數學課本有教過埃式篩法
- 當 \(x\) 是質數時,他的倍數都不是
- \(O(\frac{N}{2}+\frac{N}{3}+\frac{N}{5}...)\sim O(N\log{\log{N}})\)
- 若不只枚舉質數的倍數:
- \(O(\frac{N}{1}+\frac{N}{2}+\frac{N}{3}+...) \sim O(N\log{N})\)
bool prime[N];
bool build(int n){
fill(prime+2,prime+N,true);
for(int i=2;i<N;i++){
if(prime[i]){
for(int j=i+i;j<N;j+=i) prime[j]=false;
}
}
}質數篩法
- \(O(N\log{\log{N}})\) 已經夠小了,所以就不教線性篩
- 有需要可以參考暑培的簡報
質因數分解
- 單個的話可以 \(O(\sqrt{N})\) 暴力找
- 若是多個的話只要知道每個數字的最小質因數,就可以 \(O(\log{x_i})\) 時間找出
- 枚舉質因數倍數時順便更新他們的最小質因數
- \(O(N\log{\log{N}}+Q\log{N})\)
- 給 \(N\) 個數字,請找出 \(\max\{\gcd(x_i,x_j)_{i \neq j}\}\)
- \(1 \leq n \leq 2 \cdot 10^5\)
- \(0 \leq x_i \leq 10^6\)
- 既然要最大 \(gcd\) , 假設答案是 \(x\)
- 那 \(n\) 個數字至少要存在兩個 \(x\) 的倍數
- 用和篩法類似作法找到每個數字有幾個倍數存在
- 之後從大到小找到第一個存在至少兩個倍數即可
- 因為是枚舉所有數的倍數,時間複雜度是 \(O(N\log{N})\)
#include<bits/stdc++.h>
#define int long long
#define mp make_pair
#define eb emplace_back
#define rep(n) for(int i=0;i<n;i++)
#define rep2(n) for(int j=0;j<n;j++)
#define F first
#define S second
#define all(v) v.begin(),v.end()
#define Woody
#ifdef Woody
#define quick ios::sync_with_stdio(0);cin.tie(0);
#else
#define quick
#endif
const int INF=1e18;
const int N=1e6+7;
using namespace std;
int cnt[N];
signed main(){
quick
fill(cnt,cnt+N,0);
int n;
cin>>n;
rep(n){
int x;
cin>>x;
cnt[x]++;
}
for(int i=N-1;i>=1;i--){
int ans=0;
for(int j=i;j<N;j+=i){
ans+=cnt[j];
}
if(ans>1) {
cout<<i<<"\n";
return 0;
}
}
- 給你 \(n\) 個數字 , 求互質對數
- \(1 \leq n,x_i \leq 10^6\)
- 令 \(ans[i]\) 代表最大公因數為 \(i\) 的對數
- 互質對數等於 \(\binom{n}{2}\) - \(\sum_{i>=2}{ans[i]}\)
- \(cnt(i)\) 代表公因數為 \(i\) 的個數
- \(ans[i]=cnt(i)-\sum_{i|j} {ans[j]}\)
#pragma GCC optimzize("Ofast,no-stack-protector")
#include<bits/stdc++.h>
#define int long long
#define quick ios::sync_with_stdio(0);cin.tie(0);
#define rep(x,a,b) for(int x=a;x<=b;x++)
#define repd(x,a,b) for(int x=a;x>=b;x--)
#define lowbit(x) (x&-x)
#define sz(x) (int)(x.size())
#define F first
#define S second
#define all(x) x.begin(),x.end()
#define mp make_pair
#define eb emplace_back
using namespace std;
typedef pair<int,int> pii;
void debug(){
cout<<"\n";
}
template <class T,class ... U >
void debug(T a, U ... b){
cout<<a<<" ",debug(b...);
}
const int N=1e6+7;
const int INF=1e18;
int x[N];
int sum[N];
int ans[N];
signed main(){
quick
int n;
cin>>n;
rep(i,1,n) cin>>x[i],sum[x[i]]++;
rep(i,2,N-1){
int cnt=sum[i];
for(int j=i+i;j<N;j+=i){
if(sum[j]) cnt+=sum[j];
}
ans[i]=cnt*(cnt-1)/2;
}
int S=0;
repd(i,N-1,2){
for(int j=i+i;j<N;j+=i) ans[i]-=ans[j];
S+=ans[i];
}
cout<<n*(n-1)/2-S<<"\n";
return 0;
練習
輾轉相除法(歐幾里得算法)
- 對於 \(a \geq b ,\gcd(a,b)=\gcd(a-b,b)\)
- pf: \(d | a \land d | b \rightarrow d | (a-b)\)
- \(\gcd(a,b)=\gcd(b,a \mod b)\)
- \(a \mod b \leq \frac{a}{2}\)
- \(O(\log{\max(a,b)})\)
輾轉相除法
- 實作上用遞迴直到 \(b=0\) 停止
- 也可以用迴圈常數較小
- c++ 有內建 __gcd(a,b) 可以不用自己寫
int gcd(int a, int b){
while(b > 0){
int t = a % b;
a = b;
b = t;
}
return a;
}
int gcd(int a,int b){
if(b==0) return a;
return gcd(b,a%b);
}擴展歐基里得算法
- 給你 \(ax+by=c\) 求一組 \(x,y\) 的整數解
貝祖定理
- 給你 \(ax+by=c\) 求一組 \(x,y\) 的整數解
- 存在一組 \(x,y\) 整數解若且為若 \(\gcd(a,b)|c\)
- 因此只要找到 \(ax+by=\gcd(a,b)\) 的解在進行倍數縮放即可
擴展歐基里得算法
- \(ax+by=\gcd(a,b) \\ =\gcd(b,a \mod b) \\ =\gcd(b,a-\lfloor\frac{a}{b}\rfloor\cdot b) \\ = bx'+(a-\lfloor\frac{a}{b}\rfloor\cdot b)y' \\ = ay'+b(x'-\lfloor\frac{a}{b}\rfloor \cdot y')\)
- 先找出 \(bx'+(a-\lfloor\frac{a}{b}\rfloor\cdot b)y'\) 的解
- 那就可另 \(x=y',y=x'-\lfloor\frac{a}{b}\rfloor \cdot y')\)
- 當 \(b\) 是 0 時回傳 \(x=1,y=0\)
擴展歐基里得算法
pair<int,int> exgcd(int a,int b){
if(b==0) return mp(1,0);
pii res=exgcd(b,a%b);
return res.S,res.F-a/b*res.S;
}- 先找出 \(bx'+(a-\lfloor\frac{a}{b}\rfloor\cdot b)y'\) 的解
- 那就可另 \(x=y',y=x'-\lfloor\frac{a}{b}\rfloor \cdot y')\)
- 當 \(b\) 是 0 時回傳 \(x=1,y=0\)
- 時間複雜度會和輾轉相除法相同 \(O(\log{\max(a,b)})\)
- 有了一組解找其他很容易
擴展歐基里得算法
- 前面討論的是\(a,b\)為正整數,負數時細節比較複雜
- 可以把所有負號先去掉,最後再補上

歐拉函數
- \(\phi(n)\) 為不超過 \(1 \sim n-1\) 與 \(n\) 互質各數
- \((n,m)=1 \rightarrow \phi(nm)=\phi(n) \cdot \phi(m)\)
- \(n=p_1^{k_1}p_2^{k_2}...,p_{m}^{k_m} \rightarrow \phi(n)=n\prod_{i=1}^{m}{\frac{p_i-1}{p_i}}\)


取模的定理
- 歐拉定理: \(a^k \equiv a^{k \mod \phi(m)} \pmod{m}, (a,m)=1\)
- 費馬小定理: \(a^{p}=a \pmod {p}\) , 在 \(p\) 是質數下
- 在 a 不是 p 的倍數的時候,\(a^{p-1} \equiv 1 \pmod{p}、a^n \equiv a^{n \mod{p-1}} \pmod{p}\)
模逆元
- \(x\cdot x^{-1} \equiv 1\)
- \(x^{-1}\) 稱為模逆元
- 要如何求?
- 通常題目的模數都是超大質數
- 費馬小定理: \(a^{p}=a \pmod {p}\) , 在 \(p\) 是質數下
- \(a \cdot a^{p-2} \equiv 1 \equiv a \cdot a^{-1}\)
- 快速冪搞定
int inv(int x,int Mod){
return qpow(x,Mod-2,Mod)
}模逆元
- 模逆元其實有線性建表
- 但\(O(n)\) 次快速冪其實就夠快了
- 想要學可以參考 wiwihorz 2023暑培簡報
- 練習題: Exponentiation II
矩陣
矩陣
- 類似二維陣列
- 列(row) 是橫的、行(column)是直的
- 中國的行列和台灣剛好相反
- \(n\) row,\(m\) column記做 \(n\times m\)
- 第\(i\) row, 第 \(j\) column記做 \(M_{ij}\) 和陣列相同
- 下方是 \(3 \times 2\) 矩陣

矩陣運算
- 有加法、減法、乘法
- \(A+B=C \ A-B=C\) 就是對應位置的元素去加減,因此矩陣大小必須都相同
- \(A \times B=C \ , \ C_{ij}=\sum{A_{ik}\cdot {B_{kj}}}\)
- \(A:n\times m , B: m \times p \rightarrow C: n\times p\)
- 時間複雜度會是 \(O(nmp)\)
- 矩陣滿足分配律,所以可以用快速冪求矩陣次方 \(A^{n}\)
矩陣
- 矩陣可以幹嘛?
- Fibonacci Numbers
- 請輸出第 \(n\) 項費式數列
- \(1 \leq n \leq 10^{18}\)
矩陣
- 輸出第 \(n\) 項費式數列 \(\mod{10^9+7}\)
- 直接按照順序算一定會 TLE
- 擺上矩陣+矩陣快速冪搞定


- 給一張 \(n\) 個節點的有向圖
- 求從節點 \(1\) 走恰 \(k\) 步到節點 \(n\) 的方法數。答案 \(\mod{10^9 + 7}\) 後輸出。
- \(n\leq 100、k\leq 10^9\)
- 設 \(dp[k][x]\) 代表第 \(k\) 次恰好走到 \(x\) 的方法數
- \(dp[k][x]=\sum{dp[k-1][j] \cdot W_{j,x}}\)
- \(W_{i,j}\) 為1代表存在邊 \(i \rightarrow j\)
- 可看成是 \(DP^{k}=DP^{k-1} \cdot W=DP^{0}\cdot W^{k}\)
- => 矩陣快速冪
#include<bits/stdc++.h>
//#define Woody
#define int long long
#define lowbit(x) (x&-x)
#define max3(a,b,c) max(max(a,b),c)
#define rep(n) for(int i=0;i<n;i++)
#define mp make_pair
#define eb emplace_back
#define F first
#define S second
#define SZ(a) (int)(a.size())
#define all(v) v.begin(),v.end()
#define SETIO(s) ifstream cin(s+".in");ofstream cout(s+".out");
#ifdef Woody
#define quick ios::sync_with_stdio(0);cin.tie(0);
#else
#define quick
#endif
#define INF 1e12
using namespace std;
typedef pair<int,int> pii;
const int N=101;
const int Mod=1e9+7;
int l[2][N][N]={0};
struct matrix{
vector<vector<int >> v;
int n;
void init(int x,int pos)
{
n=x;
v.assign(n,vector<int>(n,0));
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
v[i][j]=l[pos][i][j];
}
}
}
void multiply(matrix &k){
vector<vector<int> > v2;
v2.assign(n,vector<int>(n,0));
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
for(int m=0;m<n;m++){
v2[i][j]+=v[i][m]*k.v[m][j];
v2[i][j]%=Mod;
}
}
}
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
v[i][j]=v2[i][j];
}
}
}
}st,ans;
signed main(){
quick
int n,m,k;
cin>>n>>m>>k;
//fill_n(l[0],2*N*N,0);
while(m--){
int a,b;
cin>>a>>b;
l[0][--a][--b]++;
}
for(int i=0;i<n;i++){
l[1][i][i]=1;
}
st.init(n,0);
ans.init(n,1);
while(k>0){
if(k&1){
ans.multiply(st);
}
st.multiply(st);
k>>=1;
}
cout<<ans.v[0][n-1]<<"\n";
}
高斯消去法
- 給你 \(n\) 元一次 \(m\) 條聯立方程式求解
- 高斯消去法 == 某種加減消去法
高斯消去法
- 目標是讓對角線左側都是 0
- 也就是第 \(i\) 條式子的 \(x_1 \sim x_{i-1}\) 都是 \(0\)
- 這樣只要最後從最後一條式依序往上帶入就有全部的解
高斯消去法
- 讓第 \(i\) 條式子的 \(x_{i,1} \sim x_{i,i-1}\) 都是 \(0\)
- 作法超級暴力
- 當第 \(i\) 條式子 \(x_{i,i} =0\) 就往下找到第一個元素 \(i\) 不是 \(0\) 的,並交換兩條式子
- 之後枚舉下方所有式子,利用加減消去讓其第 \(i\) 個都是 \(0\)
- \(n\) 條式子就可以搞定
- \(O(n^2m)\)
求解
- 拿有用的前 \(n\) 條式子就可以倒著求解
- 如果剩下式子有帶進去無解就是不合法
裸題
- TIOJ 2170 / 2019 北市賽 pD 地圖編修
- 帶模聯立方程組求解,保證唯一解。
- 加減消去法的除法改成找模逆元
Code
#pragma GCC optimzize("Ofast,no-stack-protector")
#include<bits/stdc++.h>
#define int long long
#define quick ios::sync_with_stdio(0);cin.tie(0);
#define rep(x,a,b) for(int x=a;x<=b;x++)
#define repd(x,a,b) for(int x=a;x>=b;x--)
#define lowbit(x) (x&-x)
#define sz(x) (int)(x.size())
#define F first
#define S second
#define all(x) x.begin(),x.end()
#define mp make_pair
#define eb emplace_back
using namespace std;
typedef pair<int,int> pii;
void debug(){
cout<<"\n";
}
template <class T,class ... U >
void debug(T a, U ... b){
cout<<a<<" ",debug(b...);
}
const int N=1e3+7;
const int INF=1e18;
vector<int> v[N];
int M;
int qpow(int a,int b){
int ret=1;
while(b>0){
if(b&1) ret=ret*a%M;
a=a*a%M;
b>>=1;
}
return ret;
}
int inv(int x){
return qpow(x,M-2);
}
signed main(){
quick
int n;
cin>>n>>M;
vector<int> st(n);
rep(i,0,n-1) cin>>st[i];
rep(i,0,n-1){
rep(j,0,n-1){
int x;
cin>>x;
v[j].eb(x);
}
}
rep(i,0,n-1){
v[i].eb(st[i]);
}
rep(i,0,n-1){
if(!v[i][i]){
rep(j,i+1,n-1){
if(v[j][i]){
swap(v[j],v[i]);
break;
}
}
}
rep(j,i+1,n-1){
int mul=(M-v[j][i]%M*inv(v[i][i])%M)%M;
rep(x,i,n){
v[j][x]=(v[j][x]+mul*v[i][x])%M;
}
}
}
vector<int> ans(n);
repd(i,n-1,0){
int s=v[i][n];
rep(j,i+1,n-1){
s=s-v[i][j]*ans[j]%M;
s=(s+M)%M;
}
ans[i]=s*inv(v[i][i])%M;
}
rep(i,0,n-1){
cout<<ans[i]<<" \n"[i==n-1];
}
return 0;
}
組合
公式
- \(n!\) \(n\) 個相異物排列的方法數
- \(n!=1\cdot 2 \cdot ... \cdot n\)
- \(n^m\) \(m\)個相異物品和 \(n\) 種相異狀態,物品搭配狀態的方法數
- \(P^{n}_{k}=\frac{n!}{(n-k)!}\) \(n\)個相異物品選 \(k\) 個排列方法數
- \(C^{n}_{k}=\frac{n!}{k!(n-k)!}\) \(n\) 個不同物品選 \(k\) 個的方法數
- \(H^{n}_{k}=C^{n+k-1}_{n}=\frac{(n+k-1)!}{k!(n-1)!}\) \(n\) 個相同物品分 \(k\) 堆的方法數
如何求 \(C^{n}_{m}\)
- 通常題目要求的都是答案 \(\pmod{Mod}\)
- 巴斯卡定理 \(C^{n}_{m}=C^{n-1}_{m-1}+C^{n-1}_{m}\)
- \(O(n^2)\)
- 或是按照定義 \(C^{n}_{m}=\frac{n!}{(n-m)!(m!)} \equiv (n! \cdot inv((n-m)!) \cdot inv(m!)\)
- 只要 \(O(n\log{n})\) 預處理 \(n! , inv(n!)\) 即可
機率和期望值
- \(p\)=發生的數量/總事件數量
- 期望值 \(E=\sum{p_i\cdot w_i}\)
排容原理
- 簡單來說就是每次扣掉多算的、加回少算的

練習題
- CSES Math
- 可以參考我之前寫的時候的筆記
題目講解
有一個 \(8\times 8\) 的方格,一開始每個格子上都有一台機器人,每次機器人會在不超出邊界的情況下從上下左右中可走的方向隨機選一個並移動,每個被選到的機率相同。求在 \(k\) 次移動後空格數量的期望值為多少。
\(k≤100\)
- 給定一數的質因數分解: 共 \(n\) 種質因數,每個為 \(x_i^{k_i}\)
- 要求以下3個數值 \(\pmod {10^9+7}\)
- 因數數量
- 因數總和
- 因數乘積
-
給定 求 \(1 \sim n\) 中有幾個數至少有幾個為陣列 \(a\) 中任一數的倍數。 \(a\) 陣列長度為 \(k\)
\(1≤n≤10^{18} \\ 1≤k≤20 \\ 2≤a≤10^5\)
數學
By yuhung94
數學
- 300