AcWing算法基础课----数学知识(三) 笔记 ( 高斯消元 + 求组合数 )

高斯消元 O(n^3)

解方程: 无解 \ 无穷多解 \ 有唯一解

利用线性代数初等行列变换
1.把某一行乘一个非零的数
2.交换某两行
3.把某行若干倍加到另一行上去

变换之后结果与解的关系:
1.完美阶梯型 唯一解
2.不完美阶梯型 0=非零 无解
3.不完美阶梯型 0=0 无穷解

浮点数判断是否为零 需要和eps比

算法步骤:

  • 枚举每一列c
    1. 找到绝对值最大的一行
    2. 将该行换到最上面
    3. 将改行第一个数变为1
    4. 将下面所有行的第c列消成0

模板:

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

求组合数

1.递归法求组合数

O(n^2)
10万组 1<=b<=a<=2000

递推式:Cab = 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;
通过预处理逆元的方式求组合数 —— 模板题 AcWing 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;
}

预处理阶乘的余数和阶乘逆元的余数 O(nlogn)
1万组 1<=b<=a<=1e5



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

2.Lucas定理

20组 1<=b<=a<=1e18 1<=p<=1e5

证明过程:
在这里插入图片描述

模板:

若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 res = 1;
    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)     // 通过定理求组合数C(a, b)
{
    int res = 1;
    for (int i = 1, j = a; i <= b; i ++, j -- )
    {
        res = (LL)res * j % p;
        res = (LL)res * qmi(i, p - 2) % p;
    }
    return res;
}

int lucas(LL a, LL b)
{
    if (a < p && b < p) return C(a, b);
    return (LL)C(a % p, b % p) * lucas(a / p, b / p) % p;
}

3.分解质因数法求组合数

模板:

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

卡特兰数

给定n个0和n个1,它们按照某种顺序排成长度为2n的序列,满足任意前缀中0的个数都不少于1的个数的序列的数量为: Cat(n) = C(2n, n) / (n + 1)

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值