数值分析:线性方程组的直接解法(上)

news/2024/11/27 20:34:40/文章来源:https://www.cnblogs.com/SXWisON/p/18570862

提纲

  1. 背景介绍
  2. 三角方程组
  3. Gauss消去法
  4. 附录

一、背景介绍

1.1 线性方程组的相关概念

线性方程组在解决现实师姐问题中直接产生,最小二乘数据拟合、微分方程边值问题和初边值问题的数值解产生了大量的线性方程组。
线性方程组系数矩阵的类型分别有

  1. 稠密型(dense):几乎所有元素都是非零的
  2. 稀疏型(sparse):有大量零元素
  3. 带状的(banded)
  4. 三角状(triangular)
  5. 块状的(block structure)

解线性方程组的方法可以分为两类

  1. 直接法(direct method)
    经过有限步四则运算可球的方程组准确解的方法
  2. 迭代法(iterative method)
    从一个近似值出发,构造某种算法,使其逐步接近准确解

大多科学计算应用经过建模和数值离散后,都可归结为如下两种形式方程组的求解:
方程组形式

\[\begin{cases} a_{11}x_1+a_{12}x_2+\cdots+a_{1n}x_n=b_1,\\ a_{21}x_1+a_{22}x_2+\cdots+a_{2n}x_n=b_2,\\ \cdots\\ a_{n1}x_1+a_{n2}x_2+\cdots+a_{nn}x_n=b_n \end{cases} \]

矩阵形式

\[\begin{bmatrix} a_{11} & a_{12} &\cdots &a_{1n}\\ a_{21} & a_{22} &\cdots &a_{2n}\\ &&\cdots&\\ a_{n1} & a_{n2} &\cdots &a_{nn}\\ \end{bmatrix} \begin{bmatrix} x_1\\x_2\\\vdots\\x_n \end{bmatrix}= \begin{bmatrix} b_1\\b_2\\\vdots\\b_n \end{bmatrix} \]

\(Ax=b\)有唯一解\(\iff A\)非奇异

C++中的线性方程组


在线性代数中,一矩阵的尺寸通常称为阶数(order)或维度(dimension)。以下示例代码在主函数中定义了稀疏矩阵\(A\),常向量\(b\)和解向量\(x\)

Eigen库中,可以采用Eigen::MatrixXd表示矩阵类型,采用Eigen::VectorXd表示向量类型。矩阵和向量的尺寸可以在创建时进行设定。

需要注意的是,Eigen库只中Eigen::VectorXd默认为列向量,如果需要将其作为行向量进行运算,需要在使用时进行转置,例如:X.transpose()

即使没有硬性的要求,但还是建议读者使用const size_t类型的变量单独存储矩阵的尺寸,这将使得代码维护变得更容易。

#include <iostream>
#include <Eigen/Dense>int main() {// 矩阵的阶数const size_t order = 6;// 定义系数矩阵 AEigen::MatrixXd A(order, order); // 指定尺寸为 order * order// 定义常向量 bEigen::VectorXd b(order);  // 指定尺寸为 order * 1 // 定义解向量 xEigen::VectorXd x(order); // 指定尺寸为 order * 1
}

采用直接法求解线性方程组的求解器通常包含三个输入,即:系数矩阵\(A\)、常向量\(b\)和解向量\(x\)
在进行求解前,首先应当检查输入是否符合求解器要求,例如针对上三角矩阵的求解器需要检查系数矩阵是否为上三角矩阵;一般的,输入应满足三个要求:

  1. 系数矩阵\(A\)为方阵
  2. 系数矩阵\(A\)的行数等于常向量\(b\)的行数
  3. 系数矩阵\(A\)的列数等于解向量\(x\)的行数

矩阵的行数可以通过.rows()方法得到
矩阵的列数可以通过.cols()方法得到

该方法对于向量同样适用,特别的,向量的列数总是1

以下给出参考的实现:

void size_check(const Eigen::MatrixXd& A,const Eigen::VectorXd& b,const Eigen::VectorXd& x)
{// 检查A是否为方阵if (A.rows() != A.cols()) {throw std::invalid_argument("Error: The coefficient matrix of the system of equations is not a square matrix.");}// 检查系数矩阵A的尺寸是否与常向量b的尺寸匹配if (A.rows() != b.rows()) {throw std::invalid_argument("Error: The order of the coefficient matrix A does not match the order of the constant vector b.");}// 检查系数矩阵A的尺寸是否与解向量x的尺寸匹配if (A.cols() != x.rows()) {throw std::invalid_argument("Error: The order of the coefficient matrix A does not match the order of the solution vector x.");}
}void solve(const Eigen::MatrixXd& A,const Eigen::VectorXd& b,Eigen::VectorXd& x) 
{// 检查尺寸是否合适size_check(A, b, x);// 求解// ...
}

在实际实现时有几个应注意的细节

为什么不将解向量\(x\)作为输出?
将解向量\(x\)作为输出的函数的使用方式为:ans=solve(A,b),如果返回值的尺寸与变量ans的尺寸不一致则会导致程序错误。为了避免该问题,必须在创建变量ans时设置尺寸,并在求解前检查尺寸,伪代码如下

Eigen::VetorXd x(order);if (x.rows() == A.cols()) { // 尺寸检查x = solve(A,b);
}

显然,形如ans=solve(A,b,x)的求解器更为易用,其类型检查可以在函数内部完成,这带来了更好的封装性、可维护性。

在必要的时候添加&const关键字
在传递函数参数时,&关键字表明了该传参方式为引用传参,区别于普通传参,引用传参方式使得函数无需在其内部拷贝一个副本,而是可以直接在原变量上进行操作。无需拷贝副本显著降低了程序的性能开销。
对于普通传参,const关键字表明内部拷贝的副本为常变量。对于引用传参,const关键字表明该函数不具有修改该变量的权限,只具备读取(访问)的权限。

三角方程组

下三角方程组


\[\begin{bmatrix} a_{11} &&&\\ a_{21} & a_{22} &&\\ \vdots&&\ddots&\\ a_{n1} & a_{n2} &\cdots &a_{nn}\\ \end{bmatrix} \begin{bmatrix} x_1\\x_2\\\vdots\\x_n \end{bmatrix}= \begin{bmatrix} b_1\\b_2\\\vdots\\b_n \end{bmatrix} \]

解法:前代法(Forward substitution)

\[\begin{cases} x_1 = b_1/a_{11}\\ x_2 = (b_2-a_{21}x_1)/a_{22}\\ \cdots\\ x_i = (b_i-\sum_{j=1}^{i-1}a_{ij}x_j)/a_{ii}, i=1,2,\cdots,n \end{cases} \]

下三角矩阵判断
Eigen库并没有提供直接的判断矩阵是否为下三角矩阵的方法,因此采用了如下的判断方法:

  1. 首先提取矩阵的严格上三角部分(不包含对角线)
  2. 判断其是否全部为零,如果严格上三角部分全部为零,那么其为下三角矩阵

前代法求解

  1. 检查输入尺寸是否匹配
  2. 判断系数矩阵是否为下三角矩阵
  3. 采用前代法求解。

\[\begin{align*} x_i = (b_i-\sum_{j=1}^{i-1}a_{ij}x_j)/a_{ii}, i=1,2,\cdots,n \tag{2.1} \end{align*} \]

外层循环用于遍历解向量\(x\)的每个元素,从下标0开始,遍历至下标n-1结束。循环内部分布实现式\((2.1)\)的计算,对于求和部分,嵌套内层循环实现。

矩阵/向量元素访问
在访问矩阵/向量的元素时元素,采用括号运算符进行访问。

#include "check.h"bool isLowerTriangular(const Eigen::MatrixXd& A) {// 获取矩阵的严格上三角部分(不包括对角线)Eigen::MatrixXd upperTriangularPart = A.triangularView<Eigen::StrictlyUpper>();// 检查严格上三角部分是否全为零return upperTriangularPart.isZero();
}void forward_substitution(const Eigen::MatrixXd& A,const Eigen::VectorXd& b,Eigen::VectorXd& x) 
{// 检查尺寸是否匹配size_check(A, b, x);// 判断系数矩阵是否为下三角矩阵if (!isLowerTriangular(A)) {throw std::invalid_argument("Error: The matrix is not lower triangular.");}for (size_t i = 0; i < A.rows(); ++i) {x(i) = b(i);for (size_t j = 0; j + 1 <= i; ++j) { // j < i - 1x(i) -= A(i, j) * x(j);}x(i) /= A(i, i);}
}

注意事项

应当注意C++中的数组索引是从0开始的,Eigen库也沿用了这一习惯。

在求和\(\sum_{j=1}^{i-1}a_{ij}x_j\)的实现中,很容易错误的使用j<=i-1作为循环的终止条件,这实际上有一个风险,当i=0的时候,i-1并不是-1,而是最大的size_t类型的数,这将导致终止条件错误,因此,应当用j+1<=i

上三角方程组


\[\begin{bmatrix} a_{11} & a_{12} &\cdots &a_{1n}\\ & a_{22} &\cdots &a_{2n}\\ &&\ddots&\vdots\\ &&&a_{nn}\\ \end{bmatrix} \begin{bmatrix} x_1\\x_2\\\vdots\\x_n \end{bmatrix}= \begin{bmatrix} b_1\\b_2\\\vdots\\b_n \end{bmatrix} \]

解法:回代法(Back substitution)

\[\begin{cases} x_n = b_n/a_{nn}\\ x_{n-1} = (b_{n-1}-a_{n-1,n}x_n)/a_{n-1,n-1}\\ \cdots\\ x_i = (b_i-\sum_{j=1}^{i-1}a_{ij}x_j)/a_{ii}, i=n,n-1,\cdots,1 \end{cases} \]

上三角矩阵判断
Eigen库并没有提供直接的判断矩阵是否为上三角矩阵的方法,因此采用了如下的判断方法:

  1. 首先提取矩阵的严格下三角部分(不包含对角线)
  2. 判断其是否全部为零,如果严格下三角部分全部为零,那么其为上三角矩阵

回代法求解

  1. 检查输入尺寸是否匹配
  2. 判断系数矩阵是否为上三角矩阵
  3. 采用回代法求解。

\[\begin{align*} x_i = (b_i-\sum_{j=1}^{i-1}a_{ij}x_j)/a_{ii}, i=n,n-1,\cdots,1 \tag{2.2} \end{align*} \]

外层循环用于遍历解向量\(x\)的每个元素,从下标n-1开始,遍历至下标0结束。循环内部分布实现式\((2.2)\)的计算,对于求和部分,嵌套内层循环实现。

bool isUpperTriangular(const Eigen::MatrixXd& A) {// 获取矩阵的严格下三角部分(不包括对角线)Eigen::MatrixXd lowerTriangularPart = A.triangularView<Eigen::StrictlyLower>();// 检查严格下三角部分是否全为零return lowerTriangularPart.isZero();
}void back_substitution(const Eigen::MatrixXd& A,const Eigen::VectorXd& b,Eigen::VectorXd& x)
{// 检查尺寸是否匹配size_check(A, b, x);// 判断系数矩阵是否为上三角矩阵if (!isUpperTriangular(A)) {throw std::invalid_argument("Error: The matrix is not upper triangular.");}size_t n = A.rows();for (size_t i = n - 1; i != size_t(-1); --i) { // i != -1x(i) = b(i);for (size_t j = i + 1; j <= n - 1; ++j) {x(i) -= A(i, j) * x(j);}x(i) /= A(i, i);}
}

注意事项

外层循环的遍历是从下标n-1开始,遍历至下标0结束;一般习惯性的写法是,以i>=0作为实际条件,但应当注意,size_t类型是非负的,事实上,对于size_t类型的变量,当其值为0时再做-1,其值为size_t(-1),因此,可以采用i!=size_t(-1)作为截止条件

高斯消元法

一般高斯消元法


高斯消元法(Gaussian Elimination)是一种用于求解线性方程组的经典方法。它通过逐步消去未知数,将方程组化为上三角形式,然后通过回代法求解未知数。高斯消元法主要分为两个步骤:前向消元和后向回代,本文中将以前向消元为例展开讨论。

前向消元(Forward Elimination)
前向消元法是从第一列开始,通过一些列的行变换,逐渐将原矩阵变换为一个上三角矩阵。假定矩阵的尺寸为\(N*N\),那么高斯消元法需要进行\(N-1\)次,在第\(i\)时执行如下操作:

  1. 选择主元:选择第\(i\)列的元素\(A_{i,i}\)作为主元
  2. 消去操作:通过将第\(i\)行的适当倍数加到其他行,使得当前列的其它元素变为零。

消去操作的公式如下:

\[\begin{cases} m_{ik}&={a_{ik}^{(k)}}/{a_{kk}^{(k)}}\\ a_{ij}^{(k+1)}&=a_{ij}^{(k)}-m\cdot a_{kj}^{(k)}\\ b_{i}^{(k+1)}&=b_{i}^{(k)}-m\cdot b_{k}^{(k)}\\ k=1,2,\dots,n-1\\ i,j=k+1.\dots,n \end{cases}\tag{3.1} \]

矩阵的第一步消元过程可以参考以下公式:

\[\left[ \begin{array}{cccc|c} a_{11}^{(1)} & a_{12}^{(1)} &\cdots &a_{1n}^{(1)}&b_1^{(1)}\\ a_{21}^{(1)} & a_{22}^{(1)} &\cdots &a_{2n}^{(1)}&b_2^{(1)}\\ &&\cdots&&\vdots\\ a_{n1}^{(1)} & a_{n2}^{(1)} &\cdots &a_{nn}^{(1)}&b_n^{(1)}\\ \end{array} \right] \longrightarrow \left[ \begin{array}{cccc|c} a_{11}^{(1)} & a_{12}^{(1)} &\cdots &a_{1n}^{(1)}&b_1^{(1)}\\ 0 & a_{22}^{(2)} &\cdots &a_{2n}^{(2)}&b_2^{(2)}\\ &&\cdots&&\vdots\\ 0 & a_{n2}^{(2)} &\cdots &a_{nn}^{(2)}&b_n^{(2)}\\ \end{array} \right] \]

在下述程序中,采样行向量相减的方式实现高斯消元法,相较于逐个元素相减,代码更简洁易懂,易维护。

void simple_gauss_elimination(Eigen::MatrixXd& A, Eigen::VectorXd& b) {// 检查尺寸是否匹配size_check(A, b);size_t n = A.rows();// 逐步消元为上三角矩阵for (size_t k = 0; k < n - 1; ++k) {// 提取矩阵的第k行Eigen::VectorXd temp = A.row(k);// 将第i列索引大于i的元素消为0for (size_t i = k + 1; i < n; ++i) {// 计算比值double m = A(i, k) / A(k, k);// 消元A.row(i) -= m * temp;b(i) -= m * b(k);}}
}

改进的高斯消元法


\(a^{(k)}_{kk}\to 0\),则\(m=a_{ik}^{(k)}/a_{kk}^{(0)}\to\infty\),此时直接用高斯消元法求解线性方程组是会由于舍入误差的扩大,而导致解失真。

因此在原高斯消元法的基础上,可以做改进,新增主元的选择过程,该方法称为列主元法,具体流程如下:

  1. 寻找第\(k\)列中第\(k\)行到第\(n\)行最大的元素,记为\(a_{jk}\)

\[\text{pivot}=\max_{k\leq i\leq n}\big|A(i,k)\big| \]

  1. 将第\(j\)行与第\(k\)行交换
  2. 进行高斯消元法
void gauss_elimination(Eigen::MatrixXd& A, Eigen::VectorXd& b) {// 检查尺寸是否匹配size_check(A, b);size_t n = A.rows();// 逐步消元为上三角矩阵for (size_t k = 0; k < n; ++k) {// 选择主元size_t j = k;double max = abs(A(j, k)); for (size_t i = k + 1; i < n; ++i) {double d = abs(A(i, k));if (d > max) { // 选择绝对值最大的元素j = i; max = d;}}// 交换主元if (j != k) {Eigen::VectorXd temp = A.row(j);A.row(j) = A.row(k);A.row(k) = temp;double temp_b = b(j);b(j) = b(k);b(k) = temp_b;}// 将第i列索引大于i的元素消为0for (size_t i = k + 1; i < n; ++i) {// 计算比值double m = A(i, k) / A(k, k);// 消元A.row(i) -= m * A.row(k);b(i) -= m * b(k);}}
}

注意事项

对方程\(Ax=b\)的系数矩阵\(A\)和常向量\(b\)同时做行变换时,方程的解\(x\)不变。

基于高斯消元法的一般线性方程求解


对于一般的线性方程组,可以先用高斯消元法将系数矩阵转化为上三角矩阵,再通过回代法求解。

void gauss_solve(Eigen::MatrixXd A,Eigen::VectorXd b,Eigen::VectorXd& x)
{// 检查尺寸是否匹配size_check(A, b, x);// 高斯消元法转为上三角矩阵gauss_elimination(A, b);// 通过回代法求解back_substitution(A, b, x);
}

注意事项

切忌舍本逐末,虽然添加引用修饰符可以一定程度上提升性能,但是这会导致稀疏矩阵\(A\)和常向量\(b\)被修改,而用户往往容易忽略这一点,因此为了保证安全性,此处不使用引用传参

截止到目前,对系数矩阵\(A\)为下三角形矩阵的线性方程组有两种求解方法,一种是采用前代法,一种是采用高斯消元结合回代法,在附录中我们对同一组数据采用两种方法分别计算结果,进行交叉验证

附录

功能测试方法


构建函数(方法)的测试程序流程如下:

  1. 从函数(方法)的名称中提取缩写,作为名声空间的前缀
  2. 定义测试函数,命名为test(),如果需要可以设计多个,例如:test1(), test2()
  3. 实现测试函数,一般来说,有以下步骤:①生成数据,②调用方法,③打印数据以及结果
  4. 在主函数中,调用该名声空间下的测试函数test(),一般需要使用try-catch结构

示例代码如下:

namespace SMP{void test() {std::cout << "Hello World!";}
}int main() {try{SMP::test();}catch (const std::exception& e) {std::cerr << "Error: " << e.what() << std::endl;}
}

在后续的附录内容中,将省略main函数的设计,读者只需按照上述方法调用即可。

前代法测试


namespace FWD{// test for forward_substitution()void test() { // 矩阵的阶数const size_t order = 5;// 定义系数矩阵 AEigen::MatrixXd A(order, order);// 定义常向量 bEigen::VectorXd b(order);// 定义解向量 xEigen::VectorXd x(order);// 设置矩阵为随机数A.setRandom();b.setRandom();// 处理为方便手算的数字A = (1.5 + A.array()) * 2;;b *= 10;A = A.array().round().matrix();b = b.array().round().matrix();// 将严格上三角部分设置为零,使其成为下三角矩阵A.triangularView<Eigen::StrictlyUpper>().setZero();// 前代法forward_substitution(A, b, x);// 输出结果std::cout << "A=\n" << A << "\n";std::cout << "b=\n" << b << "\n";std::cout << "x=\n" << x << "\n";}
}

效果展示
程序的输出如下图所示(经过拼接),经检验,该计算结果正确(读者感兴趣的可以手算一下试试)。
description

回代法测试


namespace BCK{// test for back_substitution()void test() { // 矩阵的阶数const size_t order = 5;// 定义系数矩阵 AEigen::MatrixXd A(order, order);// 定义常向量 bEigen::VectorXd b(order);// 定义解向量 xEigen::VectorXd x(order);// 设置矩阵为随机数A.setRandom();b.setRandom();// 处理为方便手算的数字A = (1.5 + A.array()) * 2;;b *= 10;A = A.array().round().matrix();b = b.array().round().matrix();// 将严格下三角部分设置为零,使其成为上三角矩阵A.triangularView<Eigen::StrictlyLower>().setZero();// 回代法back_substitution(A, b, x);// 输出结果std::cout << "A=\n" << A << "\n";std::cout << "b=\n" << b << "\n";std::cout << "x=\n" << x << "\n";}
}

效果展示
程序的输出如下图所示(经过拼接),经检验,该计算结果正确.
description

一般高斯消元法测试


namespace S_GSE {// test for simple_gauss_eliminationvoid test() { // 矩阵的阶数const size_t order = 5;// 定义系数矩阵 AEigen::MatrixXd A(order, order);// 定义常向量 bEigen::VectorXd b(order);// 设置矩阵为随机数A.setRandom();b.setRandom();// 调整显示精度为小数点后两位std::cout << std::fixed << std::setprecision(2);// 输出消元前矩阵std::cout << "A=\n" << A << "\n";std::cout << "b=\n" << b << "\n";// 前代法simple_gauss_elimination(A, b);// 输出消元后矩阵std::cout << "A=\n" << A << "\n";std::cout << "b=\n" << b << "\n";}
}

效果展示
程序的输出如下图所示(经过拼接),显示精度为小数点后两位;经检验,该计算结果正确.
description

列主元法改进的高斯消元法测试


namespace GSE {// test for simple_gauss_eliminationvoid test() { // 矩阵的阶数const size_t order = 5;// 定义系数矩阵 AEigen::MatrixXd A(order, order);// 定义常向量 bEigen::VectorXd b(order);// 设置矩阵为随机数A.setRandom();b.setRandom();// 调整显示精度为小数点后两位std::cout << std::fixed << std::setprecision(2);// 输出消元前矩阵std::cout << "A=\n" << A << "\n";std::cout << "b=\n" << b << "\n";// 前代法gauss_elimination(A, b);// 输出消元后矩阵std::cout << "A=\n" << A << "\n";std::cout << "b=\n" << b << "\n";}
}

程序的输出如下图所示(经过拼接),显示精度为小数点后两位;经检验,该计算结果正确.
description

高斯+回代法求解


namespace GS_SOLVE{void test1() {const size_t order = 5;Eigen::MatrixXd A(order, order);Eigen::VectorXd b(order);Eigen::VectorXd x(order);// 设置矩阵为随机数A.setRandom();b.setRandom(); b = (1.0 + b.array()) * 5;// 前代法gauss_solve(A, b, x);// 输出结果std::cout << std::fixed << std::setprecision(2);std::cout << "A=\n" << A << "\n";std::cout << "b=\n" << b << "\n";std::cout << "x=\n" << x << "\n";}void test2() {const size_t order = 5;Eigen::MatrixXd A(order, order);Eigen::VectorXd b(order);Eigen::VectorXd x1(order);Eigen::VectorXd x2(order);// 设置矩阵为随机数A.setRandom();b.setRandom(); b = (1.0 + b.array()) * 5;// 将上三角部分设置为零,使其成为下三角矩阵A.triangularView<Eigen::StrictlyUpper>().setZero();// 高斯gauss_solve(A, b, x1);// 前代法forward_substitution(A, b, x2);// 输出结果std::cout << std::fixed << std::setprecision(2);std::cout << "GS_solve:\n" << "x1=\n" << x1 << "\n";std::cout << "back_stt:\n" << "x2=\n" << x2 << "\n";}
}

测试1
函数GS_SOLVE::test1()用于测试高斯求解是否能够正常工作,该程序的输出如下图所示(经过拼接),显示精度为小数点后两位;经检验,该计算结果正确.
description

测试2
函数GS_SOLVE::test2()采用交叉验证法,分别采用前代法,一种是采用高斯消元结合回代法求解系数矩阵\(A\)为下三角矩阵的线性方程组,并对比计算结果;经检验,结果各方面功能正常。
description

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/842488.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第十三次作业

1、MS08-067、MS10-018漏洞复现,对比bind和reverse攻击模式的区别 MS08-067: 输入msfconsole进入msf终端查找ms08-067漏洞利用模块使用查找出来的攻击模块查看该攻击模块下所需的配置信息设置攻击payload查看并设置靶机地址查看targets选择攻击目标再次检查配置信息关闭目标地…

TIA下SCL做交通灯控制程序完善

和前一篇一样,我希望在没有点击停止按钮时,多次点击开始按钮不要中断当前流程。 在FB程序变量的static组添加BOOL类型running变量。修改SCL程序如下 IF #stopbtn=1 THEN   #CurrentState := 4;   #Running := 0;END_IF; IF (#Running = 0) AND (#startbrn=1) THEN …

【随手记录】IDEA里面pom文件被忽略,变灰、画横线处理

在setting --> Build -->Build Tools --> Maven 下找到ignored files选项,去掉pom文件的勾选框,重新加载项目即可!

大模型问答的工作流程

从问题输入到输出经历五个步骤文本分词: 大模型会将输入文本转化成单词,词语,词组,符号 分词向量化: 将分词转化成向量为了让计算机更好的理解 大模型推理: 推理时大模型会根据输入计算出下一个输出的分词的集合 分词输出: 从推理出来的集合中选择一个分词,将新的结果再进行大模…

[75] (NOIP集训) NOIP2024 加赛 8

A.flandre 我的做法是,所有数离散化之后扔进桶里,去枚举选择 \([i,+\infty)\) 内的数的贡献,在所有的 \(i\) 里取一个最大值作为答案 lbtl 指出可能存在最优答案是选择 \([i+1,+\infty)\) 内的所有数与值为 \(i\) 的部分数的数据 和 lbtl 交涉后尝试构造一组相同元素只选后一…

P10974 换根 dp 解题报告

题目传送门 题目大意: 给定一颗无根树,有一个节点是源点,度数为 \(1\) 的点是汇点,树上的边有最大流量。除源点和汇点外,其它点不储存水,即流入该点的水量之和等于从该点流出的水量之和。整个水系的流量定义为原点单位时间内能发出的水量。 现在需要求出:在流量不超过最…

CH592/CH585工具更新说明_USB篇

①打开USB更新工具 ②保证芯片没有供电以及没有GPIO灌电的前提下,将PB22接到低电平GND上(进入BootLoader),再插入USB线供电,之后点击软件中的Search Device即可搜索到设备 ③点击Download下载程序即可 ④烧录成功样例

H5-6 列表标签 有序列表

1、有序标签有序列表是一列项目,列表项目使用数学进行标记。有序列表始于<ol>标签。每个列表始于<li>标签。 <ol><li></li><li></li></ol> 2、type属性: <ol>的属性type拥有的选项1表示列表项目用数字标号(1,2,…

rust学习十二、测试

测试从来不是一件简单的事情,我本人深有体会! 书本作者引用了很重要的话:软件测试是证明 bug 存在的有效方法,而证明其不存在时则显得令人绝望的不足 (Edsger W. Dijkstra 在其 1972 年的文章【谦卑的程序员】(“The Humble Programmer”)) 注:Edsger W. Dijkstra在1…

NOIP2024加赛8

如此状态,如何NOIP状态很不好,恼了。虚拟机太卡了,根本交不上去。 flandre 发现选取的肯定是从大到小排序后的一个后缀,然后就做完了,时间复杂度\(O(n\log n)\)。点此查看代码 #include<bits/stdc++.h> using namespace std; #define rep(i,s,t,p) for(int i = s;i …

根据条件显示不同背景色

1. 概述1.1 问题描述 在 FineReport 中制作报表时,经常遇到在满足一定条件下「单元格/行/列」需要显示为不同的背景色,那么该如何实现呢? 1.2 解决思路 可以通过添加「条件属性>背景」来实现。 设置「当前格子/当前行/当前列」的原理一样,本文以「当前格子」和「当前行」…

Linux下打包Qt应用程序

linux下打包应用程序 非常复杂 接下来一步一步实现 第一步:下载linuxdeployqt程序 我已经保存在了百度网盘,记住qt5用老一些的版本 第二步:下载好后重命名为linuxdeployqt好用一点 然后将其移动到/usr/local/bin目录下 并且授权 记住 一定要授权 检查是否成功 sudo lin…