数论之高斯消元解线性方程,组合数的四种求法

308 阅读6分钟

目录

高斯消元法

高斯消元步骤:

组合数导航

组合数(一)(组合数简单性质)

组合数(二)(快速幂求逆元)

组合数(三)(卢卡斯定理)

组合数(四)(高精度)

组合数(五)(卡特兰数)


高斯消元法

例题:

高斯消元步骤:

!!!枚举每一列col

  1. 找到绝对值最大的那一行
  2. 将该行换到最上面
  3. 将该行的第一个数变成1
  4. 将下面所有行的第col列的数消成0(通过原系数-原系数*上一个方程为什么要变成1的原因)

核心思想:

将多组方程构造为阶梯型(三角形):即如下图所示,那么就可以由下层依次推出上层的解

多种情况:(可以看上面的人话版本)

  1. 无解:若在最后化成的上三角形矩阵中,正对角线中某个元素为0,但其所在行的最后一列元素不为0时,此时矩阵无解
  2. 有无数解:若在最后化成的上三角形矩阵中,存在正对角线中某个元素为0,且其所在行的最后一列元素也为0时,此时矩阵有无穷组解
  3. 有唯一解:若在最后化成的上三角形矩阵中,不存在正对角线中某个元素为0,此时矩阵有唯一解

代码实现:

#include <iostream>
#include <algorithm>
#include <cmath>

using namespace std;

const int N = 110;
/*  C++浮点数存在误差,不能直接判断0,要判断是否小于一个很小的数,
    如果小于这个很小的数,就认为是0,如小于1e-6*/
const double eps = 1e-6;

int n;
double a[N][N];


int gauss()
{
    int c, r;// 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;
        // 如果这一列中最大值已经是0了,直接continue进入下一列
        if (fabs(a[t][c]) < eps) continue;

        // ② 将这行换到最上面
        // 从当前列c开始交换后面的值到第一行
        for (int i = c; i < n + 1; i++) swap(a[t][i], a[r][i]);
        // ③ 将该行第一个数变成1
        // 将r行中c列后的每一个元素除上一个a[r][c](该行的第一个不为零的元素)
        for (int i = n; i >= c; i--) a[r][i] /= a[r][c];

        // ④ 用这一行将下面每一行的该列清0
        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];//删成0的操作

        r++; // r表示消0后最后剩余的行数
    }

    // 无解和无穷多解的判断
    if (r < n)
    {
        // 如果出现了等号左右一个为0一个非0,则说明无解
        for (int i = r; i < n; i++)
            if (fabs(a[i][n]) > eps)
                return 2;
        // 否则说明有无穷多解
        return 1;
    }

    // 回溯计算每个值xi
    for (int i = n - 1; i >= 0; i--)
        for (int j = i + 1; j < n; j++)
            a[i][n] -= a[j][n] * a[i][j];

    // 有唯一解
    return 0;
}

int main()
{
    cin >> n;
    // 存储线性方程组的增广矩阵
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n + 1; j++)
            cin >> a[i][j];
    // 执行高斯消元
    int t = gauss();

    // 如果 t = 0,有唯一解
    if (t == 0)
    {   // %.2lf 保留两位小数
        for (int i = 0; i < n; i++) printf("%.2lf\n", a[i][n]);
    }
    // 如果 t = 1,线性方程组存在无数解
    else if (t == 1) puts("Infinite group solutions");
    // 否则无解
    else puts("No solution");

    return 0;
}

组合数导航

组合数(一)(组合数简单性质)

组合数的性质:

解释:共有a个苹果,我从中选择b个苹果 等价于

在a个苹果中独立出来a-1个苹果和1个苹果

我从a-1个苹果中选择b个苹果(这b个苹果中不包括那个被独立处出来的那一个苹果)

+我从a-1个苹果中选择b-1个苹果(这b-1个苹果中包括了那个被独立出来那一个的苹果)

#include <iostream>
#include <algorithm>
using namespace std;

const int N = 2010, mod = 1e9 + 7;
int c[N][N];
void init()
{
    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;
}

int main()
{
    int n;

    init();

    cin >> n;

    while (n--)
    {
        int a, b;
        cin >> a >> b;

        printf("%d\n", c[a][b]);
    }

    return 0;
}

组合数(二)(快速幂求逆元)

思路:

  1. 预处理阶乘
  2. 预处理逆元的阶乘
  3. 由公式求出组合数

图解: [AcWing] 886. 求组合数 II(C++实现)求组合数第二种题型模板题_cloud的博客-CSDN博客

#include <iostream>
#include <algorithm>
using namespace std;

typedef long long LL;
const int N = 100010, mod = 1e9 + 7;
int fact[N], infact[N];
// 快速幂 :求a的k次方模p的结果
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;
}

int main()
{
    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;
    }


    int n;
    scanf("%d", &n);
    while (n--)
    {
        int a, b;
        scanf("%d%d", &a, &b);
        // 由阶乘和逆元通过公式算出c[a][b]的值
        printf("%d\n", (LL)fact[a] * infact[b] % mod * infact[a - b] % mod);
    }

    return 0;
}

组合数(三)(卢卡斯定理)

[AcWing] 887. 求组合数 III(C++实现)求组合数第三种题型(卢卡斯定理)模板题_cloud的博客-CSDN博客

#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

// 快速幂求a的k次方模p
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;
}

//由定义计算组合数
int C(int a, int b, int p)
{
    if (b > a) return 0; // 如果b > a,组合数为0

    int res = 1;
    // 由定义计算组合数
    for (int i = 1, j = a; i <= b; i ++, j -- )
    {
    	// 乘j
        res = (LL)res * j % p;
        // 除i,注意:除i等同于乘i的逆元
        res = (LL)res * qmi(i, p - 2, p) % p;
    }
    return res;
}

// 卢卡斯定理
int lucas(LL a, LL b, int p)
{
	// 如果a和b都小于p,从定义来计算组合数C
    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;
}


int main()
{
    int n;
    cin >> n;

    while (n -- )
    {
        LL a, b;
        int p;
        cin >> a >> b >> p;
        // 卢卡斯定理求组合数
        cout << lucas(a, b, p) << endl;
    }

    return 0;
}

组合数(四)(高精度)

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

const int N = 5010;
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;
        }
    }
}

// ② 计算n的阶乘中素数p的次数
int get(int n, int p)
{
    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;
}


int main()
{
    int a, b;
    cin >> a >> b;

    // ① 分解质因数
    get_primes(a);

    // ② 计算阶乘中每个素数的次数
    for (int i = 0; i < cnt; i++)
    {
        int p = primes[i];
        // 用分母的质因子减去分子的质因子就是组合数C的质数的次数
        sum[i] = get(a, p) - get(a - b, p) - get(b, p);
    }

    // ③ 高精度乘法
    vector<int> res;
    res.push_back(1);

    for (int i = 0; i < cnt; i++)//素数的个数
        for (int j = 0; j < sum[i]; j++)//组合数C的质数的次数
            res = mul(res, primes[i]);

    // 输出该值
    for (int i = res.size() - 1; i >= 0; i--) printf("%d", res[i]);
    puts("");

    return 0;
}

组合数(五)(卡特兰数)

原理:[AcWing] 889. 满足条件的01序列(C++实现)求组合数第五种题型(卡特兰数)模板题_cloud的博客-CSDN博客

#include <iostream>
#include <algorithm>
using namespace std;

typedef long long LL;
const int N = 100010, mod = 1e9 + 7;

// 快速幂
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;
}


int main()
{
    int n;
    cin >> n;

    int a = n * 2, b = n;
    int res = 1;
    for (int i = a; i > a - b; i--) res = (LL)res * i % mod;

    for (int i = 1; i <= b; i++) res = (LL)res * qmi(i, mod - 2, mod) % mod;

    res = (LL)res * qmi(n + 1, mod - 2, mod) % mod;

    cout << res << endl;

    return 0;
}