演算法基礎課相關程式碼模板
試除法判定質數 —— 模板題 luogu 866. 試除法判定質數
bool is_prime(int x)
{
if (x < 2) return false;
for (int i = 2; i <= x / i; i ++ )
if (x % i == 0)
return false;
return true;
}
試除法分解質因數 —— 模板題 luogu 867. 分解質因數
void divide(int x)
{
for (int i = 2; i <= x / i; i ++ )
if (x % i == 0)
{
int s = 0;
while (x % i == 0) x /= i, s ++ ;
cout << i << ' ' << s << endl;
}
if (x > 1) cout << x << ' ' << 1 << endl;
cout << endl;
}
樸素篩法求素數 —— 模板題 luogu 868. 篩質數
int primes[N], cnt; // primes[]儲存所有素數
bool st[N]; // st[x]儲存x是否被篩掉
void get_primes(int n)
{
for (int i = 2; i <= n; i ++ )
{
if (st[i]) continue;
primes[cnt ++ ] = i;
for (int j = i + i; j <= n; j += i)
st[j] = true;
}
}
線性篩法求素數 —— 模板題 luogu 868. 篩質數
int primes[N], cnt; // primes[]儲存所有素數
bool st[N]; // st[x]儲存x是否被篩掉
void get_primes(int n)
{
for (int i = 2; i <= n; i ++ )
{
if (!st[i]) primes[cnt ++ ] = i;
for (int j = 0; primes[j] <= n / i; j ++ )
{
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
試除法求所有約數 —— 模板題 luogu 869. 試除法求約數
vector<int> get_divisors(int x)
{
vector<int> res;
for (int i = 1; i <= x / i; i ++ )
if (x % i == 0)
{
res.push_back(i);
if (i != x / i) res.push_back(x / i);
}
sort(res.begin(), res.end());
return res;
}
約數個數和約數之和 —— 模板題 luogu 870. 約數個數, luogu 871. 約數之和
如果 N = p1^c1 * p2^c2 * ... *pk^ck
約數個數: (c1 + 1) * (c2 + 1) * ... * (ck + 1)
約數之和: (p1^0 + p1^1 + ... + p1^c1) * ... * (pk^0 + pk^1 + ... + pk^ck)
歐幾里得演算法 —— 模板題 luogu 872. 最大公約數
int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
求尤拉函式 —— 模板題 luogu 873. 尤拉函式
int phi(int x)
{
int res = x;
for (int i = 2; i <= x / i; i ++ )
if (x % i == 0)
{
res = res / i * (i - 1);
while (x % i == 0) x /= i;
}
if (x > 1) res = res / x * (x - 1);
return res;
}
篩法求尤拉函式 —— 模板題 luogu 874. 篩法求尤拉函式
int primes[N], cnt; // primes[]儲存所有素數
int euler[N]; // 儲存每個數的尤拉函式
bool st[N]; // st[x]儲存x是否被篩掉
void get_eulers(int n)
{
euler[1] = 1;
for (int i = 2; i <= n; i ++ )
{
if (!st[i])
{
primes[cnt ++ ] = i;
euler[i] = i - 1;
}
for (int j = 0; primes[j] <= n / i; j ++ )
{
int t = primes[j] * i;
st[t] = true;
if (i % primes[j] == 0)
{
euler[t] = euler[i] * primes[j];
break;
}
euler[t] = euler[i] * (primes[j] - 1);
}
}
}
快速冪 —— 模板題 luogu 875. 快速冪
求 m^k mod p,時間複雜度 O(logk)。
int qmi(int m, int k, int p)
{
int res = 1 % p, t = m;
while (k)
{
if (k&1) res = res * t % p;
t = t * t % p;
k >>= 1;
}
return res;
}
擴充套件歐幾里得演算法 —— 模板題 luogu 877. 擴充套件歐幾里得演算法
// 求x, y,使得ax + by = gcd(a, b)
int exgcd(int a, int b, int &x, int &y)
{
if (!b)
{
x = 1; y = 0;
return a;
}
int d = exgcd(b, a % b, y, x);
y -= (a/b) * x;
return d;
}
高斯消元 —— 模板題 luogu 883. 高斯消元解線性方程組
// a[N][N]是增廣矩陣
int gauss()
{
int c, r;
for (c = 0, r = 0; c < n; c ++ )
{
int t = r;
for (int i = r; i < n; i ++ ) // 找到絕對值最大的行
if (fabs(a[i][c]) > fabs(a[t][c]))
t = i;
if (fabs(a[t][c]) < eps) continue;
for (int i = c; i <= n; i ++ ) swap(a[t][i], a[r][i]); // 將絕對值最大的行換到最頂端
for (int i = n; i >= c; i -- ) a[r][i] /= a[r][c]; // 將當前行的首位變成1
for (int i = r + 1; i < n; i ++ ) // 用當前行將下面所有的列消成0
if (fabs(a[i][c]) > eps)
for (int j = n; j >= c; j -- )
a[i][j] -= a[r][j] * a[i][c];
r ++ ;
}
if (r < n)
{
for (int i = r; i < n; i ++ )
if (fabs(a[i][n]) > eps)
return 2; // 無解
return 1; // 有無窮多組解
}
for (int i = n - 1; i >= 0; i -- )
for (int j = i + 1; j < n; j ++ )
a[i][n] -= a[i][j] * a[j][n];
return 0; // 有唯一解
}
遞推法求組合數 —— 模板題 luogu 885. 求組合數 I
// c[a][b] 表示從a個蘋果中選b個的方案數
for (int i = 0; i < N; i ++ )
for (int j = 0; j <= i; j ++ )
if (!j) c[i][j] = 1;
else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
透過預處理逆元的方式求組合數 —— 模板題 luogu 886. 求組合數 II
首先預處理出所有階乘取模的餘數fact[N],以及所有階乘取模的逆元infact[N]
如果取模的數是質數,可以用費馬小定理求逆元
int qmi(int a, int k, int p) // 快速冪模板
{
int res = 1;
while (k)
{
if (k & 1) res = (LL)res * a % p;
a = (LL)a * a % p;
k >>= 1;
}
return res;
}
// 預處理階乘的餘數和階乘逆元的餘數
fact[0] = infact[0] = 1;
for (int i = 1; i < N; i ++ )
{
fact[i] = (LL)fact[i - 1] * i % mod;
infact[i] = (LL)infact[i - 1] * qmi(i, mod - 2, mod) % mod;
}
Lucas定理 —— 模板題 luogu 887. 求組合數 III
若p是質數,則對於任意整數 1 <= m <= n,有:
C(n, m) = C(n % p, m % p) * C(n / p, m / p) (mod p)
int qmi(int a, int k, int p) // 快速冪模板
{
int res = 1 % p;
while (k)
{
if (k & 1) res = (LL)res * a % p;
a = (LL)a * a % p;
k >>= 1;
}
return res;
}
int C(int a, int b, int p) // 透過定理求組合數C(a, b)
{
if (a < b) return 0;
LL x = 1, y = 1; // x是分子,y是分母
for (int i = a, j = 1; j <= b; i --, j ++ )
{
x = (LL)x * i % p;
y = (LL) y * j % p;
}
return x * (LL)qmi(y, p - 2, p) % p;
}
int lucas(LL a, LL b, int p)
{
if (a < p && b < p) return C(a, b, p);
return (LL)C(a % p, b % p, p) * lucas(a / p, b / p, p) % p;
}
分解質因數法求組合數 —— 模板題 luogu 888. 求組合數 IV
當我們需要求出組合數的真實值,而非對某個數的餘數時,分解質因數的方式比較好用:
1. 篩法求出範圍內的所有質數
2. 透過 C(a, b) = a! / b! / (a - b)! 這個公式求出每個質因子的次數。 n! 中p的次數是 n / p + n / p^2 + n / p^3 + ...
3. 用高精度乘法將所有質因子相乘
int primes[N], cnt; // 儲存所有質數
int sum[N]; // 儲存每個質數的次數
bool st[N]; // 儲存每個數是否已被篩掉
void get_primes(int n) // 線性篩法求素數
{
for (int i = 2; i <= n; i ++ )
{
if (!st[i]) primes[cnt ++ ] = i;
for (int j = 0; primes[j] <= n / i; j ++ )
{
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
int get(int n, int p) // 求n!中的次數
{
int res = 0;
while (n)
{
res += n / p;
n /= p;
}
return res;
}
vector<int> mul(vector<int> a, int b) // 高精度乘低精度模板
{
vector<int> c;
int t = 0;
for (int i = 0; i < a.size(); i ++ )
{
t += a[i] * b;
c.push_back(t % 10);
t /= 10;
}
while (t)
{
c.push_back(t % 10);
t /= 10;
}
return c;
}
get_primes(a); // 預處理範圍內的所有質數
for (int i = 0; i < cnt; i ++ ) // 求每個質因數的次數
{
int p = primes[i];
sum[i] = get(a, p) - get(b, p) - get(a - b, p);
}
vector<int> res;
res.push_back(1);
for (int i = 0; i < cnt; i ++ ) // 用高精度乘法將所有質因子相乘
for (int j = 0; j < sum[i]; j ++ )
res = mul(res, primes[i]);
卡特蘭數 —— 模板題 luogu 889. 滿足條件的01序列
給定n個0和n個1,它們按照某種順序排成長度為2n的序列,滿足任意字首中0的個數都不少於1的個數的序列的數量為: Cat(n) = C(2n, n) / (n + 1)
NIM遊戲 —— 模板題 luogu 891. Nim遊戲
給定N堆物品,第i堆物品有Ai個。兩名玩家輪流行動,每次可以任選一堆,取走任意多個物品,可把一堆取光,但不能不取。取走最後一件物品者獲勝。兩人都採取最優策略,問先手是否必勝。
我們把這種遊戲稱為NIM博弈。把遊戲過程中面臨的狀態稱為局面。整局遊戲第一個行動的稱為先手,第二個行動的稱為後手。若在某一局面下無論採取何種行動,都會輸掉遊戲,則稱該局面必敗。
所謂採取最優策略是指,若在某一局面下存在某種行動,使得行動後對面面臨必敗局面,則優先採取該行動。同時,這樣的局面被稱為必勝。我們討論的博弈問題一般都只考慮理想情況,即兩人均無失誤,都採取最優策略行動時遊戲的結果。
NIM博弈不存在平局,只有先手必勝和先手必敗兩種情況。
定理: NIM博弈先手必勝,當且僅當 A1 ^ A2 ^ … ^ An != 0
公平組合遊戲ICG
若一個遊戲滿足:
- 由兩名玩家交替行動;
- 在遊戲程序的任意時刻,可以執行的合法行動與輪到哪名玩家無關;
- 不能行動的玩家判負;
則稱該遊戲為一個公平組合遊戲。
NIM博弈屬於公平組合遊戲,但城建的棋類遊戲,比如圍棋,就不是公平組合遊戲。因為圍棋交戰雙方分別只能落黑子和白子,勝負判定也比較複雜,不滿足條件2和條件3。
有向圖遊戲
給定一個有向無環圖,圖中有一個唯一的起點,在起點上放有一枚棋子。兩名玩家交替地把這枚棋子沿有向邊進行移動,每次可以移動一步,無法移動者判負。該遊戲被稱為有向圖遊戲。
任何一個公平組合遊戲都可以轉化為有向圖遊戲。具體方法是,把每個局面看成圖中的一個節點,並且從每個局面向沿著合法行動能夠到達的下一個局面連有向邊。
Mex運算
設S表示一個非負整數集合。定義mex(S)為求出不屬於集合S的最小非負整數的運算,即:
mex(S) = min{x}, x屬於自然數,且x不屬於S
SG函式
在有向圖遊戲中,對於每個節點x,設從x出發共有k條有向邊,分別到達節點y1, y2, …, yk,定義SG(x)為x的後繼節點y1, y2, …, yk 的SG函式值構成的集合再執行mex(S)運算的結果,即:
SG(x) = mex({SG(y1), SG(y2), …, SG(yk)})
特別地,整個有向圖遊戲G的SG函式值被定義為有向圖遊戲起點s的SG函式值,即SG(G) = SG(s)。
有向圖遊戲的和 —— 模板題 luogu 893. 集合-Nim遊戲
設G1, G2, …, Gm 是m個有向圖遊戲。定義有向圖遊戲G,它的行動規則是任選某個有向圖遊戲Gi,並在Gi上行動一步。G被稱為有向圖遊戲G1, G2, …, Gm的和。
有向圖遊戲的和的SG函式值等於它包含的各個子游戲SG函式值的異或和,即:
SG(G) = SG(G1) ^ SG(G2) ^ … ^ SG(Gm)
定理
有向圖遊戲的某個局面必勝,當且僅當該局面對應節點的SG函式值大於0。
有向圖遊戲的某個局面必敗,當且僅當該局面對應節點的SG函式值等於0。