P3389 【模板】高斯消元法
题目描述
P3389 【模板】高斯消元法 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
运行代码
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 110;
const double eps = 1e-6;
int n;
double a[N][N]; // 增广矩阵
// 交换两行
void swapRows(int row1, int row2) {
for (int j = 1; j <= n + 1; j++) {
swap(a[row1][j], a[row2][j]);
}
}
// 高斯消元函数
int gauss() {
for (int i = 1; i <= n; ++i) { // 第 i 主元
int nonzeroRow = -1;
for (int k = i; k <= n; ++k) { // 找非零行
if (fabs(a[k][i]) > eps) {
nonzeroRow = k;
break;
}
}
if (nonzeroRow == -1) return 0; // 无解
swapRows(i, nonzeroRow); // 交换非零行
double div = a[i][i];
for (int j = n + 1; j >= i; j--) { // 使主元所在列的值变为 1
a[i][j] /= div;
}
for (int k = i + 1; k <= n; k++) { // 使主元所在列下方的值变为 0
double mult = a[k][i];
for (int j = n + 1; j >= i; j--) {
a[k][j] -= mult * a[i][j];
}
}
}
for (int i = n - 1; i >= 1; i--) { // 回代
for (int j = i + 1; j <= n; j++) {
a[i][n + 1] -= a[i][j] * a[j][n + 1];
}
}
return 1; // 存在唯一解
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n + 1; j++) {
scanf("%lf", &a[i][j]);
}
}
if (gauss()) {
for (int i = 1; i < n + 1; i++) {
printf("%.2lf\n", a[i][n + 1]);
}
}
else {
puts("No Solution");
}
return 0;
}
代码思路
-
定义了一些常量和变量:
N
表示矩阵的最大大小。eps
是一个极小的误差阈值,用于判断数值是否接近零。n
表示方程组的未知数个数,也是矩阵的行数。a[N][N]
是存储增广矩阵的二维数组。
-
gauss
函数:- 外层循环
for(int i=1; i<=n; ++i)
遍历每一列,以确定主元所在的行。 - 内层第一个循环
for(int k=i; k<=n; ++k)
用于在当前列中寻找非零行,如果找到则与当前行交换。 - 如果主元所在行的主元值接近零(
fabs(a[i][i])<eps
),则表示方程组无解,函数返回 0 。 - 接下来,通过除以主元值,将主元所在列的主元变为 1(
for(int j=n+1; j>=i; j--) a[i][j] /= a[i][i];
)。 - 然后通过内层的双重循环,将主元所在列下方的元素变为 0(
for(int k=i+1; k<=n; k++) for(int j=n+1; j>=i; j--) a[k][j]-=a[k][i]*a[i][j];
)。 - 最后,通过回代过程(
for(int i=n-1; i>=1; i--)
)求解出未知数的值。
- 外层循环
-
main
函数:- 首先读取未知数的个数
n
。 - 然后通过双重循环读取增广矩阵的元素值。
- 调用
gauss
函数进行求解,如果返回 1 表示有唯一解,输出解的值;否则输出 "No Solution" 表示无解。
- 首先读取未知数的个数
P4783 【模板】矩阵求逆
题目描述
P4783 【模板】矩阵求逆 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
运行代码
#include <iostream>
#include <cstdio>
#include <cmath>
#define LL long long
using namespace std;
const int N = 405, P = 1e9 + 7;
int n;
LL a[N][N << 1];
LL quickPow(LL a, LL b) {
LL ans = 1;
while (b) {
if (b & 1) ans = ans * a % P;
a = a * a % P;
b >>= 1;
}
return ans;
}
// 交换两行
void swapRows(int row1, int row2) {
for (int j = 1; j <= 2 * n; j++) {
swap(a[row1][j], a[row2][j]);
}
}
// 高斯-约旦消元函数
bool GaussJordan() {
for (int i = 1; i <= n; ++i) { // 枚举主元的行列
int nonzeroRow = -1;
for (int k = i; k <= n; ++k) { // 找非 0 行
if (a[k][i]) {
nonzeroRow = k;
break;
}
}
if (nonzeroRow == -1) return false; // 无解
swapRows(i, nonzeroRow); // 换行
LL inv = quickPow(a[i][i], P - 2); // 求逆元
for (int k = 1; k <= n; ++k) { // 对角化
if (k == i) continue;
LL factor = a[k][i] * inv % P;
for (int j = i; j <= 2 * n; ++j) {
a[k][j] = ((a[k][j] - factor * a[i][j]) % P + P) % P;
}
}
for (int j = 1; j <= 2 * n; ++j) { // 除以主元
a[i][j] = a[i][j] * inv % P;
}
}
return true;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
scanf("%lld", &a[i][j]);
a[i][i + n] = 1;
}
}
if (GaussJordan()) {
for (int i = 1; i <= n; ++i) {
for (int j = n + 1; j <= 2 * n; ++j) {
printf("%lld ", a[i][j]);
}
puts("");
}
} else {
puts("No Solution");
}
return 0;
}
代码思路
-
定义了一些常量和变量:
N
表示矩阵的最大大小。P
是一个大质数,用于取模运算。n
表示矩阵的行数。a[N][N << 1]
是一个二维数组,用于存储矩阵。
-
quickPow
函数:这是一个用于快速计算幂次的函数,通过位运算和循环来实现高效的幂运算,并取模P
。 -
Gauss_Jordan
函数:- 这是高斯-约旦消元法的实现函数。
- 外层循环
for(int i=1;i<=n;++i)
遍历每一列,以确定主元所在的行。 - 内层第一个循环
for(int k=i; k<=n; ++k)
用于在当前列中寻找非零行,如果找到则将其与当前行交换。 - 如果主元所在行的主元值为 0,则表示方程组无解,函数返回 0 。
- 计算主元的逆元
x = quickPow(a[i][i], P - 2)
。 - 然后通过内层循环,对于非主元行进行消元操作,使得主元列除主元外的元素变为 0 。
- 最后,将主元所在行的元素除以主元。
-
main
函数:- 首先读取矩阵的行数
n
。 - 通过双重循环读取矩阵的元素值,并将单位矩阵附加在原始矩阵的右侧。
- 调用
Gauss_Jordan
函数进行求解,如果返回true
表示有解,输出求解后的结果;否则输出 "No Solution" 表示无解。
- 首先读取矩阵的行数
P3807 【模板】卢卡斯定理/Lucas 定理
题目描述
P3807 【模板】卢卡斯定理/Lucas 定理 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
运行代码
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 100010;
LL f[N], g[N];
LL qpow(LL a, int b, int p) {
LL res = 1;
a %= p;
while (b) {
if (b & 1) res = res * a % p;
a = a * a % p;
b >>= 1;
}
return res;
}
void init(int p) {
f[0] = g[0] = 1;
for (int i = 1; i <= p; i++) {
f[i] = f[i - 1] * i % p;
g[i] = qpow(f[i], p - 2, p);
}
}
LL getC(int n, int m, int p) {
if (m > n) return 0;
return f[n] * g[m] % p * g[n - m] % p;
}
int lucas(LL n, LL m, int p) {
if (m == 0) return 1;
return lucas(n / p, m / p, p) * getC(n % p, m % p, p) % p;
}
int main() {
int q;
cin >> q;
while (q--) {
LL n, m;
int p;
cin >> n >> m >> p;
init(p);
printf("%d\n", lucas(n + m, n, p));
}
return 0;
}
代码思路
-
定义了一些常量和数据类型:
N
用于设定一些数组的大小上限。定义了LL
类型(通常表示长整型)用于处理较大的整数。 -
qpow
函数:这是一个快速幂函数,用于计算a
的b
次幂对p
取模的结果。通过位运算和循环,逐位判断b
的二进制位,根据是否为 1 来决定是否累乘当前的a
,同时不断更新a
为其平方对p
取模的结果。 -
init
函数:初始化两个数组f
和g
。f
数组用于存储从 0 到p
的阶乘对p
取模的结果。g
数组用于存储从 0 到p
的阶乘的逆元对p
取模的结果。通过不断累乘计算阶乘,并使用快速幂计算阶乘的逆元。 -
getC
函数:用于计算组合数C(n, m)
对p
取模的结果。首先判断m
是否大于n
,如果是则返回 0 。然后通过已计算好的f
和g
数组来计算组合数。 -
lucas
函数:这是卢卡斯定理的实现,用于计算较大数的组合数对p
取模的结果。如果m
为 0 ,则直接返回 1 。否则,将n
和m
分别除以p
,递归计算卢卡斯定理的子问题,并乘以当前位的组合数getC(n % p, m % p, p)
对p
取模的结果。 -
main
函数:首先读取询问的次数q
。在每次询问中,读取n
、m
和p
。调用init
函数初始化相关数组。最后计算并输出lucas(n + m, n, p)
的结果。