数学知识-常用算法模板

矩阵乘法

二维 乘 二维

void mul(vector<vector<ll>>& c, vector<vector<ll>> a, vector<vector<ll>> b, int p) {

    int n = a.size();

    for (int i = 0; i < n; ++ i) {
        for (int j = 0; j < n; ++ j) {
            c[i][j] = 0;
            for (int k = 0; k < n; ++ k) {
                c[i][j] = (c[i][j] + a[i][k] * b[k][j]) % p;
            }
        }
    } 

}

一维 乘 二维

void mul(vector<ll>& c, vector<ll> a, vector<vector<ll>> b, int p) {
    int n = a.size();

    for (int i = 0; i < n; ++ i) {
        c[i] = 0;
        for (int k = 0; k < n; ++ k) {
            c[i] = (c[i] + a[k] * b[k][i]) % p;
        }
    }
}

高斯消元

// 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; // 有唯一解
}

高精度

高精度加法

// 倒着存放
vector<int> add(vector<int> a, vector<int> b) {
    vector<int> ret;
    int len = min((int)a.size(), (int)b.size());
    int at = 0, i = 0;
    for (; i < len; ++ i) {
        int t = a[i] + b[i] + at;
        at = t / 10;
        ret.push_back(t % 10);
    } 
    while (i < (int)a.size()) {
        int t = a[i] + at;
        at = t / 10;
        ret.push_back(t % 10);
        ++ i;
    }
    while (i < (int)b.size()) {
        int t = b[i] + at;
        at = t / 10;
        ret.push_back(t % 10);
        ++ i;
    }
    while (at) {
        ret.push_back(at % 10);
        at /= 10;
    }
    return ret; 
}

高精度减法

vector<int> sub(vector<int>& a, vector<int>& b) {

    vector<int> ret;
    int t = 0;
    for (int i = 0; i < a.size(); ++ i) {
        t = a[i] - t;
        if (i < b.size()) t -= b[i];
        ret.push_back((t + 10) % 10);
        if (t < 0) t = 1;
        else t = 0;
    }
    while (ret.size() > 1 && ret.back() == 0) ret.pop_back();
    return ret;
}

高精度乘法

// 高精度 乘 低精度
vector<int> mul(vector<int>& a, int b) {
    vector<int> ret;
    int t = 0;
    for (int i = 0; i < a.size() || t; ++ i) {
        if (i < a.size()) t += a[i] * b;
        ret.push_back(t % 10);
        t /= 10;
    }

    while (ret.size() > 1 && ret.back() == 0) ret.pop_back();
    return ret; 
}
// 高精度 乘 高精度
vector<int> mul(vector<int> a, vector<int> b) {
    vector<int> ret(a.size() + b.size() + 5, 0);
    for (int i = 0; i < a.size(); ++ i) {
        for (int j = 0; j < b.size(); ++ j) {
            ret[i + j] += a[i] * b[j];
        }
    }   

    for (int i = 0; i + 1 < ret.size(); ++ i) {
        ret[i + 1] += ret[i] / 10;
        ret[i] %= 10;
    } 

    while (ret.size() > 1 && ret.back() == 0) ret.pop_back();

    return ret;
}

高精度除法

int yu = 0; // 除不尽后的余数
vector<int> divv(vector<int>& a, int b) {

    vector<int> ret;
    int t = 0;
    for (int i = a.size() - 1; i >= 0; -- i ) {
        t = t * 10 + a[i];
        ret.push_back(t / b);
        t %= b;
    }
    yu = t;
    reverse(ret.begin(), ret.end());
    while (ret.size() > 1 && ret.back() == 0) ret.pop_back();
    return ret;
}

欧几里得算法

详细理论

常用

求 a, b 最大公约数即 $gcd(a, b)$
$gcd(a, b) = gcd(b, a\;mod\; b)$

int gcd(int a, int b)
{
    return b ? gcd(b, a % b) : a;
}

扩展


(裴蜀定理)

设 a, b 为正整数,则关于$ax + by = c$ 的方程有整数解当且仅当 c 是 gcd(a, b) 的倍数。


$ax + by = c$ 可以通过求 $b{x_0} + (a\;mod\;b){y_0} = c$
即可得$\begin{cases}x = {y_0} \\ y = {x_0} - \lfloor \frac{a}{b} \rfloor {y_0} \end{cases}$

该方程通解
$\begin{cases}{x_k} = x + k \cdot \frac{b}{gcd(a,b)} \\ {y_k} = y - k \cdot \frac{a}{gcd(a, b)} \end{cases}(k \in z)$

int exgcd(int a, int b, int &x, int &y)
{
    if (b == 0)
    {
        x = 1;
        y = 0;
        return a;
    }
    int d = exgcd(b, a % b, y, x); //这里交换了x和y
    y -= (a / b) * x;
    return d;
}

逆元

详细理论

欧几里德算法

同余方程

$ax \equiv 1\; (mod\; b)$

有解条件:$gcd(a, b) = 1$
即解 $ax - by = 1$
通解:${x_k} = x + kb$
即:求 $x \; mod \;b$

ll exgcd(ll a, ll b, ll &x, ll &y)// 拓欧
{
    if (b == 0)
    {
        x = 1;
        y = 0;
        return a;
    }
    ll d = exgcd(b, a % b, y, x);
    y -= (a / b) * x;
    return d;
}
ll inv(ll a, ll p)
{
    ll x, y;
    if (exgcd(a, p, x, y) != 1) // 无解的情形
        return -1;
    return (x % p + p) % p;
}

费马小定理

若 p 为质数且 $gcd(a, p) = 1$ 则有 ${a^{p-1} \equiv 1 \;(mod\;p)}$
可知$inv(a) = {a^{p-2}}\;(mod\;p)$

#define ll long long
ll qpow(ll a, ll n, ll p)// 快速幂
{
    ll ans = 1;
    while (n)
    {
        if (n & 1)
            ans = ans % p * a % p;
        a = a % p * a % p;
        n >>= 1;
    }
    return ans;
}
ll inv(ll a, ll p)
{
    return qpow(a, p - 2, p);
}

线性递推

$inv(a) = - \lfloor \frac{p}{a} \rfloor \cdot inv(p\;mod\;a)\;(mod\;p)$

#define ll long long
// 多次对不同的p使用需要清空Inv数组
ll Inv[N] = {0, 1};
ll mod(ll a, ll p)
{
    return (a % p + p) % p;
}
ll inv(ll a, ll p)
{
    if (Inv[a])
        return Inv[a];
    Inv[a] = mod(-p / a * inv(p % a, p), p);
    return Inv[a];
}

中国剩余定理

解同余方程组$\begin{cases}x\equiv 2 \;(mod\;3) \\ x\equiv 3 \;(mod\;5) \\ x\equiv 2 \;(mod\;7)\end{cases}$

余数数组为 b, 模数数组为 a

$p = \prod_{i = 1} ^ n {a_i}$

${r_i} = \frac{p}{{a_i}}$

则有

$x \equiv \sum_{i=1}^n{b_i}\cdot {r_i} \cdot inv({r_i})\mid \;(mod\;p)$

#define ll long long
ll CRT(ll a[], ll b[], ll n)  // a是模数数组,b是余数数组,n是数组长度
{
    ll p = 1, x = 0;
    for (int i = 0; i < n; ++i)
        p *= a[i];
    for (int i = 0; i < n; ++i)
    {
        ll r = p / a[i];
        x += (b[i] * r * inv(r, a[i])) % p;   // 逆元的求法参见上篇文章,或者下面有完整代码
    }
    return x % p;
}

质数筛

bool isnp[MAXN];
vector<int> primes; // 质数表
void init(int n)
{
    for (int i = 2; i <= n; i++)
    {
        if (!isnp[i])
            primes.push_back(i);
        for (int p : primes)
        {
            if (p * i > n)
                break;
            isnp[p * i] = 1;
            if (i % p == 0)
                break;
        }
    }
}

欧拉函数

详细理论

int phi(int n)
{
    int res = n;
    for (int i = 2; i * i <= n; i++)
    {
        if (n % i == 0)
            res = res / i * (i - 1); // 先除再乘防止溢出
        while (n % i == 0) // 每个质因数只处理一次,可以把已经找到的质因数除干净
            n /= i;
    }
    if (n > 1)
        res = res / n * (n - 1); // 最后剩下的部分也是原来的n的质因数
    return res;
}
int phi[N];
bool isnp[N];
vector<int> primes;
void init(int n)
{
    phi[1] = 1;
    for (int i = 2; i <= n; i++)
    {
        if (!isnp[i])
            primes.push_back(i), phi[i] = i - 1; // 性质一,指数为1的情形
        for (int p : primes)
        {
            if (p * i > n)
                break;
            isnp[p * i] = 1;
            if (i % p == 0)
            {
                phi[p * i] = phi[i] * p; // 性质二
                break;
            }
            else
                phi[p * i] = phi[p] * phi[i]; // 这时肯定互质,用性质三
        }
    }
}

Min25 筛

#define ll long long

inline ll V2IDX(ll v, ll N, ll Ndr, ll nv) {
    return v >= Ndr ? (N/v - 1) : (nv - v);
}

ll primesum(ll N) {     //求取1~N的所有质数和
    ll *S;
    ll *V;

    ll r = (ll)sqrt(N);
    ll Ndr = N/r;

    assert(r*r <= N and (r+1)*(r+1) > N);

    ll nv = r + Ndr - 1;

    V = new ll[nv];
    S = new ll[nv];

    for (ll i=0; i<r; i++) {
        V[i] = N/(i+1);
    }
    for (ll i=r; i<nv; i++) {
        V[i] = V[i-1] - 1;
    }

    for (ll i=0; i<nv; i++) {
        S[i] = V[i] * (V[i] + 1) / 2 - 1;
    }

    for (ll p=2; p<=r; p++) {
        if (S[nv-p] > S[nv-p+1]) {
            ll sp = S[nv-p+1];
            ll p2 = p*p;
            for (ll i=0; i<nv; i++) {
                if (V[i] >= p2) {
                    S[i] -= p * (S[V2IDX(V[i]/p, N, Ndr, nv)] - sp);
                } else {
                    break;
                }
            }
        }
    }

    return S[0];
}

扩展欧拉定理

详细理论

$a^p \equiv a^{b\;mod\;\varphi(m)}\;(mod\;m), 其中 gcd(a, m) = 1$

求组合数

递推

C_a^b = C_{a - 1} ^ {b} + C_{a - 1} ^ {b - 1}

// 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;

预处理逆元

$C_a^b = \frac{a!}{(a - b)!b!}$

首先预处理出所有阶乘取模的余数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;
}

分解质因数

$C_a^b = \frac{a!}{(a - b)!b!}$

当我们需要求出组合数的真实值,而非对某个数的余数时,分解质因数的方式比较好用:
    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]);

Lucas 定理

详细理论

C_m^n \equiv C_{m\;mod\;p}^{n\;mod\;p}\cdot C_{\lfloor\frac{m}{p} \rfloor}^{\lfloor\frac{n}{p}\rfloor}\;(mod\;p)

// 需要先预处理出fact[],即阶乘
inline ll C(ll m, ll n, ll p)
{
    return m < n ? 0 : fact[m] * inv(fact[n], p) % p * inv(fact[m - n], p) % p;
}
inline ll lucas(ll m, ll n, ll p)
{
    return n == 0 ? 1 % p : lucas(m / p, n / p, p) * C(m % p, n % p, p) % p;
}

大步小步算法

详细理论

$a^x\equiv b\;(mod\;m), 其中 gcd(a, m) = 1$

ll BSGS(ll a, ll b, ll m)
{
    static unordered_map<ll, ll> hs;
    hs.clear();
    ll cur = 1, t = sqrt(m) + 1;
    for (int B = 1; B <= t; ++B)
    {
        (cur *= a) %= m;
        hs[b * cur % m] = B; // 哈希表中存B的值
    }
    ll now = cur; // 此时cur = a^t
    for (int A = 1; A <= t; ++A)
    {
        auto it = hs.find(now);
        if (it != hs.end())
            return A * t - it->second;
        (now *= cur) %= m;
    }
    return -1; // 没有找到,无解
}

扩展

// 修改版的BSGS,额外带一个系数
ll BSGS(ll a, ll b, ll m, ll k = 1)
{
    static unordered_map<ll, ll> hs;
    hs.clear();
    ll cur = 1, t = sqrt(m) + 1;
    for (int B = 1; B <= t; ++B)
    {
        (cur *= a) %= m;
        hs[b * cur % m] = B; // 哈希表中存B的值
    }
    ll now = cur * k % m;
    for (int A = 1; A <= t; ++A)
    {
        auto it = hs.find(now);
        if (it != hs.end()) return A * t - it->second;
        (now *= cur) %= m;
    }
    return -INF; // 这里因为要多次加1,要返回更小的负数
}
ll exBSGS(ll a, ll b, ll m, ll k = 1)
{
    ll A = a %= m, B = b %= m, M = m;
    if (b == 1) return 0;
    ll cur = 1 % m;
    for (int i = 0;; i++)
    {
        if (cur == B) return i;
        cur = cur * A % M;
        ll d = gcd(a, m);
        if (b % d) return -INF;
        if (d == 1) return BSGS(a, b, m, k * a % m) + i + 1;
        k = k * a / d % m, b /= d, m /= d; // 相当于在递归求解exBSGS(a, b / d, m / d, k * a / d % m)
    }
}
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇