常用程式碼模板4——數學知識

kkman2000發表於2024-11-18

演算法基礎課相關程式碼模板

試除法判定質數 —— 模板題 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

若一個遊戲滿足:

  1. 由兩名玩家交替行動;
  2. 在遊戲程序的任意時刻,可以執行的合法行動與輪到哪名玩家無關;
  3. 不能行動的玩家判負;

則稱該遊戲為一個公平組合遊戲。
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。

相關文章