TonyYin's Blog

Back

高斯消元法#

高斯消元法(Gauss-Jordan elimination),可以用于解决:线性方程组求解、行列式求值、矩阵求逆等问题。

给定一个线性方程组,要求解。

一些定义#

变量 ==== 变元 == 未知数。

对于一个 mm 元线性方程组,已知系数 aija_{ij}​,未知量 xix_i​,即:

{a1,1x1+a1,2x2++a1,mxm=c1(1)a2,1x1+a2,2x2++a2,mxm=c2(2)an,1x1+an,2x2++an,mxm=cn(n)\left\{ \begin{array}{} a_{1,1}x_1+a_{1,2}x_2+\cdots+a_{1,m}x_m=c_1 &(1)\\ a_{2,1}x_1+a_{2,2}x_2+\cdots+a_{2,m}x_m=c_2 &(2)\\ \cdots\\ a_{n,1}x_1+a_{n,2}x_2+\cdots+a_{n,m}x_m=c_n &(n) \end{array} \right.

将简记为一个 nnmm 列的矩阵:

[a1,1a1,2a1,ma2,1a2,2a2,man,1an,2an,m]\left[\begin{matrix} a_{1,1} & a_{1,2} & \cdots & a_{1,m} \\ a_{2,1} & a_{2,2} & \cdots & a_{2,m} \\ \cdots & \cdots & \cdots & \cdots \\ a_{n,1} & a_{n,2} & \cdots & a_{n,m} \end{matrix} \right]

我们称这个矩阵为 系数矩阵

在系数矩阵右侧,加一列常数项,称其为 增广矩阵

[a1,1a1,2a1,ma2,1a2,2a2,man,1an,2an,m|c1c2cn]\left[\begin{matrix} a_{1,1} & a_{1,2} & \cdots & a_{1,m} \\ a_{2,1} & a_{2,2} & \cdots & a_{2,m} \\ \cdots & \cdots & \cdots & \cdots \\ a_{n,1} & a_{n,2} & \cdots & a_{n,m} \end{matrix} \middle| \begin{matrix} c_1 \\ c_2 \\ \cdots\\ c_n \end{matrix} \right]

竖线 \mid 没有其他含义,只是为了区分等式左右两侧。两侧同属一个矩阵。

为了方便表示,后面出现的 cic_i 可能会用 ai,m+1a_{i, m+1} 代替

简单情况#

简单情况开始考虑,假设恰好 n=mn=m,且每个方程都有意义,方程组形如:

{a1,1x1+a1,2x2++a1,n1xn1+a1,nxn=c1(1)0x1+a2,2x2++a2,n1xn1+a2,nxn=c2(2)0x1+0x2++a3,n1xn1+a3,nxn=c2(3)++0x1+0x2++0xn1+an,nxn=cn(n)\left\{ \begin{array}{r} &a_{1,1}x_1&+ &a_{1,2}x_2&+\cdots+ &a_{1, n-1}x_{n-1} &+ &a_{1,n}x_n=c_1 &(1)\\ &0\cdot x_1&+ &a_{2,2}x_2&+\cdots+ &a_{2, n-1}x_{n-1} &+ &a_{2,n}x_n=c_2 &(2)\\ &0\cdot x_1&+ &0\cdot x_2&+\cdots+ &a_{3, n-1}x_{n-1} &+ &a_{3,n}x_n=c_2 &(3)\\ &&&&+\cdots+\\ &0\cdot x_1&+ &0\cdot x_2&+\cdots+ &0\cdot x_{n-1} &+ &a_{n,n}x_n=c_n &(n) \end{array} \right.

矩阵为:

[a1,1a1,2a1,n1a1,n0a2,2a2,n1a2,n00a3,n1a3,n000an,n|a1,n+1a2,n+1a3,n+1an,n+1]\left[\begin{matrix} a_{1,1} & a_{1,2} & \cdots & a_{1,n-1} & a_{1,n} \\ 0 & a_{2,2} & \cdots & a_{2,n-1} & a_{2,n} \\ 0 & 0 & \cdots & a_{3,n-1} & a_{3,n} \\ \cdots & \cdots & \cdots & \cdots & \cdots \\ 0 & 0 & \cdots & 0 & a_{n,n} \end{matrix} \middle| \begin{matrix} a_{1,n+1} \\ a_{2,n+1} \\ a_{3,n+1} \\ \cdots\\ a_{n,n+1} \end{matrix} \right]

矩阵的左下角都是 00.

对于这种倒三角矩阵,最后一行代表的方程为:anmxm=an,m+1a_{nm}\cdot x_m=a_{n,m+1},可以直接求出 xm=an,m+1anmx_m=\frac{a_{n, m+1}}{a_{nm}}.

之后,我们再将 xmx_m 代入倒数第二行的方程:an1,m1xm1+an1,mxm=an1,m+1a_{n-1,m-1}\cdot x_{m-1}+a_{n-1,m}\cdot x_m=a_{n-1, m+1},求出 xm1x_{m-1}.

以此类推,就可以求出所有的未知量了。

考虑如何将原矩阵变换为倒三角矩阵

这就和一般的消元法解方程一样,要让第 xix_i 个未知数只存在于前 ii 个方程(其它方程中 xix_i 系数均为 00).

枚举列,也就是枚举未知数。当我们枚举到第 cc 列的时候,我们要让 i=c+1n[ai,c0]=0\sum_{i=c+1}^{n}[a_{i, c}\neq0]=0.

所以对于第 i[c+1,n]i\in [c+1, n] 行,整行一起变换,使 ai,c=0a_{i,c}=0

j[1,m+1],ai,jai,jac,jai,cac,c\forall j\in [1,m+1],\quad a_{i, j}\leftarrow a_{i, j}-a_{c, j}\cdot\frac{a_{i, c}}{a_{c, c}}

由于 j[1,c1]ac,j=0\forall j\in[1, c-1],a_{c, j}=0,所以上式与 j[c,m+1]j\in[c, m+1] 是等价的。

之后进行回带,把所有无关项移到等式右边。

double A[MAXN][MAXN], ans[MAXN];
void Gauss() {
	for(int c = 1; c <= n; c++) {
		for(int i = c + 1; i <= n; i++) {
			double t = A[i][c] / A[c][c];
			for(int j = c + 1; j <= n + 1; j++) {
				A[i][j] -= A[c][j] * t;
			}
		}
	}
    for(int i = n; i >= 1; i--) {
        for(int j = i + 1; j <= n; j++) a[i][n + 1] -= a[i][j] * ans[j];
        ans[i] = a[i][n + 1] / a[i][i];
    }
}
cpp

提高精度#

在消元过程中,对于第 ii 个主元,我们默认选择第 ii 行的 ai,ia_{i, i} 作为保留的元素。

为了减小误差,我们在第 ii 列的 ai,j(ji)a_{i, j}(j\geq i) 中选一个最大的数作为保留的元素,之后交换第 ii 行和第 jj 行。

显然,这样有助于精度的控制。

扩大适用范围#

前面的方法基于 n=mn=m,且方程组存在唯一解。然而,方程个数与未知数不一定相等,解的个数也不一定唯一,下面给出更普适的方法

判断解的个数#

容易发现,由 nn 个方程构成的 mm 元线性方程组,解的个数只可能为:0,1,0, 1, \infty.

为了方便判断,我们引入自由元的定义:取值不确定的变元。

当我们枚举到第 ii 个变量,且前 ii 个变量都不是自由元时,如果第 ini\sim n 行中这个变量的系数均为 00,那么第 ii 个变量是自由元。

[a1,1a1,2a1,3a1,4a1,50a2,2a2,3a2,4a2,5000a3,4a3,5000a4,4a4,50000a5,5|c1c2c3c4c5]\left[\begin{matrix} a_{1,1} & a_{1,2} & a_{1, 3} & a_{1,4} & a_{1,5} \\ 0 & a_{2,2} & a_{2, 3} & a_{2,4} & a_{2,5} \\ 0 & 0 & \color{red}{0}& a_{3,4} & a_{3,5} \\ 0 & 0 & 0 & a_{4,4} & a_{4,5} \\ 0 & 0 & 0 & 0 & a_{5,5} \end{matrix} \middle | \begin{matrix} c_{1} \\ c_{2} \\ c_{3} \\ c_{4} \\ c_{5} \end{matrix} \right]

上面的例子中,x3x_3 即为自由元。可以发现,当 x4,x5x_4, x_5 被确定之后,第三行的方程可以记为:

0x3=c3x4a3,4x5a3,5=C0\cdot x_3=c_3-x_4\cdot a_{3, 4}-x_5\cdot a_{3, 5}=C

C=0C=0,则 x3x_3 可以在 R\Bbb{R} 中任取。

C0C\neq 0,则 x3x_3 一定无解,方程组也一定无解。

另外,存在一个自由元,其值可以任取,不代表方程组有无穷组解。因为若有其它变量是无解的,则方程组无解。

出现自由元这种情况,说明有些方程之间是线性相关的,所以加减消元之后就消没了。

解决 nmn\neq m 的情况#

在枚举变量的过程当中,会发现有些变量是自由元。这时我们应当跳过这一列,并且保持行不变,处理下一个变元。

因此我们用指针 row\operatorname{row}col\operatorname{col} 表示当前所在的行和列,最后 row\operatorname{row} 的位置即为非自由元的个数

由于遇到所有自由元时,我们都保持行不变,所以可以理解为:我们把所有自由元都挪到了最后面没有被 row\operatorname{row} 遍历到的几行当中去。

这可以结合上面 5×55\times 5 的例子进行理解。

在遇到自由元 x3x_3 后,跳过第 33 列,保持 row\operatorname{row} 不变。所以第 33 行保留的主元是 x4x_4,同理第 44 行保留的主元是 x5x_5.

所以第 55 行即代表第 x3x_3,第 55 行的方程为:0×x3=c50\times x_3=c_5.

代码#

下面代码会将所有自由元保留到最后的连续几行。

因此,要判断解的情况,只需要去判断第 row+1n\operatorname{row+1}\sim n 行的系数 cc 是否为 00 即可。

//高斯消元法,返回-1为无解,0为无穷解,1为唯一解
double A[MAXN][MAXN], ans[MAXN];
int Gauss() {
	int row = 1, max_row;
	for(int col = 1; col <= m; col++) {
		max_row = row;
		for(int i = row + 1; i <= n; i++) { //提高精度 找到一列中值最大的
			if(fabs(A[i][col]) > fabs(A[max_row][col])) max_row = i;
		}
		if(fabs(A[max_row][col]) < eps)
			continue; //如果col是自由元,那么跳过这一列,但行不变
		if(max_row != row) { //swap(当前行,最大值所在的行)
			for(int i = 1; i <= m + 1; i++) swap(A[row][i], A[max_row][i]);
		}
		for(int i = row + 1; i <= n; i++) {
			double t = A[i][col] / A[row][col];
			for(int j = 1; j <= m + 1; j++) {
				A[i][j] -= A[row][j] * t;
			}
		}
		row++;
	}
	row--;
	if(row < n) {
		for(int i = row + 1; i <= n; i++) {
			//如果左边所有系数都是0,等号右边不是0,则无解
			if(abs(A[i][m + 1]) > eps) return -1;
		} //否则有无数解
		return 0;
	}
	for(int i = m; i >= 1; i--) { //回代
		for(int j = i + 1; j <= m; j++) A[i][m + 1] -= A[i][j] * ans[j];
		ans[i] = A[i][m + 1] / A[i][i];
	}
	return 1;
}
cpp

高斯-约旦消元法#

高斯-约旦消元法,是高斯消元法的另一个版本,相异之处就是这算法产生出来的矩阵是一个简化行梯阵式省去了回代过程

相比起高斯消元法,此算法的效率和精度都比较低,但代码难度也低。

算法思想#

构造方程组矩阵的方法同上,化简后的矩阵形如:

[a1,100000a2,200000a3,3000000an,m|c1c2c3cn]\left[\begin{matrix} a_{1,1} &0 & 0 &\cdots & 0 & 0 \\ 0 & a_{2,2} & 0 &\cdots & 0 & 0 \\ 0 & 0 &a_{3,3} &\cdots & 0 & 0 \\ \cdots &\cdots &\cdots &\cdots &\cdots & \cdots \\ 0 & 0 &0 &\cdots & 0 & a_{n,m} \end{matrix} \middle| \begin{matrix} c_{1} \\ c_{2} \\ c_{3} \\ \cdots\\ c_{n} \end{matrix} \right]

只保留主对角线上的元素

显然,这样就省去了回代的步骤,直接 ai,ixi=cia_{i, i}\cdot x_i=c_i 就可以解出所有未知数了。

算法实现#

在上面的高斯消元法中,有这样一句话:

当我们枚举到第 cc 列的时候,我们要让 i=c+1n[ai,c0]=0\sum_{i=c+1}^{n}[a_{i, c}\neq0]=0.

要想让最终的矩阵只保留主对角线上的元素,我们只需要把这句话改为:

当我们枚举到第 cc 列的时候,我们要让 ic[ai,c0]=0\sum_{i\neq c}[a_{i, c}\neq0]=0,也就是直接消掉其他所有行

double A[MAXN][MAXN], ans[MAXN];
void Gauss_Jordan() {
	for(int c = 1; c <= n; c++) {
		for(int i = c + 1; i <= n; i++) {
			double t = A[i][c] / A[c][c];
			for(int j = c + 1; j <= n + 1; j++) {
				A[i][j] -= A[c][j] * t;
			}
		}
	}
}
cpp

扩大适用范围#

在高斯-约旦消元法中,我们同样可以选择每列最大的数作为主元,以提高精度。

同时,我们可以用类似的方法判断方程组解的个数。

两种算法的区别只在第 1516\rm{15\sim16} 行、第 3333 行这两处。

//高斯-约旦消元法,返回-1为无解,0为无穷解,1为唯一解
double A[MAXN][MAXN], ans[MAXN];
int Gauss_Jordan() {
	int row = 1, max_row;
	for(int col = 1; col <= m; col++) {
		max_row = row;
		for(int i = row + 1; i <= n; i++) {
			if(fabs(A[i][col]) > fabs(A[max_row][col])) max_row = i;
		}
		if(fabs(A[max_row][col]) < eps)
			continue; //如果col是自由元,那么跳过这一列,但行不变
		if(max_row != row) { //swap(当前行,最大值所在的行)
			for(int i = 1; i <= n + 1; i++) swap(A[row][i], A[max_row][i]);
		}
		for(int i = 1; i <= n; i++) {
			if(i == row) continue; //每列只保留第row行的元素
			double t = A[i][col] / A[row][col];
			for(int j = 1; j <= m + 1; j++) {
				A[i][j] -= A[row][j] * t;
			}
		}
		row++;
	}
	row--;
	if(row < n) {
		for(int i = row + 1; i <= n; i++) {
			//如果左边所有系数都是0,等号右边不是0,则无解
			if(fabs(A[i][n + 1]) > eps) return -1;
		} //否则有无数解
		return 0;
	}
	for(int i = 1; i <= m; i++) {
		ans[i] = A[i][m + 1] / A[i][i];
	}
	return 1;
}
cpp

解异或方程组#

对于异或方程组,高斯消元法同样适用。

在实数方程组中,我们消掉第 vv 个变元的方法是:用第 aa 行减去第 bb 行的 kk 倍,使第 aa 行第 vv 列变为 00

在异或方程组中,若第 aa 行第 vv 列的值为 11,就将第 aa 行异或上第 bb。(按列分别异或)

由于 x{0,1}x\in\{0, 1\},所以 (ax)(bx)=(ab)x(a\cdot x)\oplus (b\cdot x)=(a\oplus b)\cdot x,因此第 aa 行异或后的方程是成立的。

注意到异或方程组的增广矩阵是 0101 矩阵(矩阵中仅含有 0011),所以我们可以使用 C++ 中的 std::bitset 进行优化,将时间复杂度降为 O(n2mω)O(\dfrac{n^2m}{\omega}),其中 nn 为元的个数,mm 为方程条数,ω\omega 一般为 3232(与机器有关)。

代码#

bitset<MAXN> A[MAXM];
int Gauss() {//A[][]是m行n列的矩阵
	for(int col = 1; col <= n; col++) {
		int x = col;
		while(x <= m && !A[x][col]) x++;
		if(x == m + 1) return 0;
		ans = max(ans, x);
		if(col != x) {//swap(A[x], A[col]);
			bitset<MAXN> tmp = A[x];
			A[x] = A[col]; A[col] = tmp;
		}
		for(int i = 1; i <= m; i++) {
			if(i != col && A[i][col]) A[i] ^= A[col];
		}
	}
	return 1;
}
cpp

模意义下的高斯消元 - 模数为质数#

对于模数为合数的情况,会在后面提到。

分析#

注意到,在实数域内的高斯-约旦消元有这样一段代码:

只要把除法操作改为乘逆元即可。

代码#

普通的高斯-约旦消元:

//......
for(int i = 1; i <= n; i++) {
    if(i == row) continue;
    double t = A[i][col] / A[row][col];
    for(int j = 1; j <= m + 1; j++) {
        A[i][j] -= A[row][j] * t;
    }
}
//......
for(int i = 1; i <= n; i++) {
    ans[i] = A[i][n + 1] / A[i][i];
}
cpp

模意义下的高斯-约旦消元:

//......
for(int i = 1; i <= n; i++) {
	if(i == row) continue;
	int k = A[i][col] * inv(a[col][col]) % mod;
	for(int j = 1; j <= m + 1; j++) {
		A[i][j] = (A[i][j] + A[col][j] * (mod - k)) % mod;
	}
}
//......
for(int i = 1; i <= n; i++) {
    ans[i] = A[i][n + 1] * inv(A[i][i]) % mod;
}
cpp

模意义下的高斯消元 - 模数为合数#

模数为合数时,有些数没有逆元,需要另寻方法。

分析#

对于模数为合数的情况,我们使用类似辗转相除的方法。

对于两行 a,ba, b,现在要消去变元 cc

在辗转相除法时,我们会用 aamodba\leftarrow a\bmod b,但现在要同时操作一整行,没法直接取模,所以要模拟一下取模的过程。

我们每次将 aa 减去 bb 的尽可能多倍(kZ+k\in \Bbb{Z}^{+}),使仍然保持 ac0a_c\geq 0,之后交换 a,ba, b,重复操作,直到 ac=0a_c=0.

具体地,所减去的倍数 k=acbck=\lfloor\dfrac{a_c}{b_c}\rfloor.

代码#

辗转相除法就不用每次找最大的换上去了,因为不需要控制精度,都是整数。

void Gauss() {
	for(int col = 1; col <= n; col++) {
		for(int row = col + 1; row <= n; row++) {
			while(a[row][col]) {
				int t = a[col][col] / a[row][col];
				for(int i = 1; i <= n; i++) {
					a[col][i] = (a[col][i] + a[row][i] * (p - t)) % p;
				}
				for(int i = 1; i <= n; i++) swap(a[col][i], a[row][i]);
			}
		}
	}
}
cpp

[JSOI2008] 球形空间产生器#

题意#

给定一个 nn 维超球上的 n+1n+1 个点的坐标,求球心坐标。保证有唯一解。

n<=10n<=10,坐标绝对值为 2000020000 以内实数。

球心:到球面任意一点距离都相等的点。

距离:若两点坐标为 A(a1,a2,an)A(a_1, a_2\cdots, a_n)B(b1,b2,,bn)B(b_1, b_2, \cdots, b_n),那么:

dis(A,B)=(a1b1)2+(a2b2)2++(anbn)2\operatorname{dis(A, B)}=\sqrt{(a_1-b_1)^2+(a_2-b_2)^2+\cdots+(a_n-b_n)^2}

分析#

设圆心坐标为 (x1,x2,,xn)(x_1, x_2, \cdots, x_n),那么容易列出一个方程组:

{i=1n(a1,ixi)2=C2(1)i=1n(a2,ixi)2=C2(2)i=1n(an,ixi)2=C2(n)i=1n(an+1,ixi)2=C2(n+1)\left\{ \begin{array}{} \sum_{i=1}^n(a_{1, i}-x_i)^2=C^2 &(1)\\ \sum_{i=1}^n(a_{2, i}-x_i)^2=C^2 &(2)\\ \cdots&\cdots\\ \sum_{i=1}^n(a_{n, i}-x_i)^2=C^2&(n)\\ \sum_{i=1}^n(a_{n+1, i}-x_i)^2=C^2&(n+1)\\ \end{array} \right.

其中 CC 就代表 nn 维超球的半径。

但这不是一个线性方程组,没法快速求解。

又注意到,求出圆心坐标,只有 nn 个未知数,而现在能列出 n+1n+1 个方程,所以考虑进行差分

差分#

对于其中的第 pp 个方程,我们对其进行整理,变为:

i=1nap,i2+i=1nxi2i=1n2ap,ixi(p)\sum_{i=1}^{n}{{a_{p, i}}^2}+\sum_{i=1}^{n}{{x_i}^2}-\sum_{i=1}^{n}{2\cdot a_{p, i}\cdot x_i} \tag{p}

同理有:

i=1nap+1,i2+i=1nxi2i=1n2ap+1,ixi(p+1)\sum_{i=1}^{n}{{a_{p+1, i}}^2}+\sum_{i=1}^{n}{{x_i}^2}-\sum_{i=1}^{n}{2\cdot a_{p+1, i}\cdot x_i} \tag{p+1}

我们用 (p+1)(p)(p+1)-(p),得到:

i=1n2(ap,iap+1,i)xi=i=1n(ap,i2ap+1,i2)(*)\sum_{i=1}^{n}{2(a_{p, i}-a_{p+1, i})x_i}=\sum_{i=1}^{n}{({a_{p, i}}^2-{a_{p+1, i}}^2)} \tag{*}

以此类推,我们能得到 nn 个形如 ()(*) 的线性方程,于是问题可以用高斯消元求解。

最终的系数矩阵为:

[2(a1,1a2,1)2(a1,2a2,2)2(a1,na2,n)2(a2,1a3,1)2(a2,2a3,2)2(a2,na3,n)2(an,1an+1,1)2(an,2an+1,2)2(an,nan+1,n)|j=1n(a1,j2a2,j2)j=1n(a2,j2a3,j2)j=1n(an,j2an+1,j2)]\left[\begin{matrix} 2(a_{1, 1}-a_{2, 1}) & 2(a_{1, 2}-a_{2, 2}) & \cdots & 2(a_{1, n}-a_{2, n}) \\ 2(a_{2, 1}-a_{3, 1}) & 2(a_{2, 2}-a_{3, 2}) & \cdots & 2(a_{2, n}-a_{3, n}) \\ \cdots & \cdots & \ddots & \cdots \\ 2(a_{n, 1}-a_{n+1, 1}) & 2(a_{n, 2}-a_{n+1, 2}) & \cdots & 2(a_{n, n}-a_{n+1, n}) \end{matrix} \middle| \begin{matrix} \sum_{j=1}^n({a_{1, j}}^2-{a_{2, j}}^2) \\ \sum_{j=1}^n({a_{2, j}}^2-{a_{3, j}}^2) \\ \vdots\\ \sum_{j=1}^n({a_{n, j}}^2-{a_{n+1, j}}^2) \end{matrix} \right]

代码#

Ubuntu Pastebin,应该不用看吧?

[SDOI2010] 外星千足虫#

题意#

给定 nn 个点,按点权的奇偶性分为两类。

给定 mm 次统计数据,每条数据包含:若干个被选中点,以及被选中的点权和的奇偶性

请判断每个点权的奇偶性。

题目保证有解,若解不唯一,输出 Cannot  Determine\rm{Cannot\;Determine}.

分析#

ai,j{0,1}a_{i, j}\in\{0, 1\} 表示第 ii 条数据中,第 jj 个点是否被选中, bi{0,1}b_i\in \{0, 1\} 表示第 ii 次统计结果的奇偶性。

xi=wimod2x_i=w_i\bmod 2 表示第 ii 个点权的奇偶性。

那么有同余方程组:

{a1,1x1+a1,2x2++a1,nxn=b1(mod2)(1)a2,1x1+a2,2x2++a2,nxn=b2(mod2)(2)am,1x1+am,2x2++am,nxn=bm(mod2)(m)\left\{ \begin{array}{} a_{1, 1}x_1+a_{1, 2}x_2+\cdots+a_{1, n}x_n=b_1\pmod{2} &&(1)\\ a_{2, 1}x_1+a_{2, 2}x_2+\cdots+a_{2, n}x_n=b_2\pmod{2} &&(2)\\ \cdots &&\cdots\\ a_{m, 1}x_1+a_{m, 2}x_2+\cdots+a_{m, n}x_n=b_m\pmod{2}&&(m)\\ \end{array} \right.

注意到,方程左右两侧都只需要考虑二进制下的第一位,因此可以转化为一个异或方程组:

{a1,1x1a1,2x2a1,nxn=b1(1)a2,1x1a2,2x2a2,nxn=b2(2)am,1x1am,2x2am,nxn=bm(m)\left\{ \begin{array}{} a_{1, 1}x_1\oplus a_{1, 2}x_2\oplus \cdots \oplus a_{1, n}x_n=b_1 &&(1)\\ a_{2, 1}x_1\oplus a_{2, 2}x_2\oplus \cdots \oplus a_{2, n}x_n=b_2 &&(2)\\ \cdots &&\cdots\\ a_{m, 1}x_1\oplus a_{m, 2}x_2\oplus \cdots \oplus a_{m, n}x_n=b_m &&(m)\\ \end{array} \right.

解异或方程组即可。

其他要求#

题目要求:输出一个不超过 MM 的正整数 KK,表明在第 KK 次统计结束后就可以确定唯一解。

对于这个要求,我们只需要在高斯消元时每次对行取 max\max 即可,详见下面代码的第 77 行。

在下面代码的消元过程中,对于第 col\rm{col} 列,我们会贪心地找到编号最小的 xx 满足 A[x][col]=1A[x][col]=1.

所以,要想确定方程的解,至少要通过前 xx 条统计数据,否则第 col\rm{col} 个变量就无法求解。

Band-Matrix#

简介#

这是个在 OI 中很经典的 trick。

Band-Matrix,带状矩阵,对这种矩阵进行高斯消元时,可以进行优化。

顾名思义,带状矩阵是长条形的,差不多是这样:

白色部分为 00,红色部分有数,其它部分先不管。

这样中间形成了一个宽度为 dd 的带,这样消元的复杂度可以降到 O(nd2)\mathcal{O}(nd^2).

常见于一些期望DP的问题。

消元流程#

在图中,绿色节点是我们要保留的主元。

注意到在这种形式下,对于任意的 Ai,iA_{i, i},其向下、向右的有效数字个数均 d1\leq d-1.

所以假设现在要消第 ii 列,那么从第 ii 行开始往下枚举 d1d-1 行,每行往右消 dd 个数字,最后能得到一个倒三角矩阵。

之后回代。回代过程的复杂度最高是 O(n2)\mathcal{O}(n^2) 的,不是复杂度瓶颈,但也可以根据带状矩阵的形状优化。

判断无解/存在自由元#

大部分题好像都有解,下面的参考资料中也有讲。

细节#

如果 nn 太大,二维数组开不出来,可以只记录带状矩阵的部分,也可以用 unordered_map.

参考资料#

浅谈高斯消元拓展之 band-matrix - Froggy 珂学家的博客

P5516 [MtOI2019]小铃的烦恼#

题意#

给定长度为 nn 的序列,第 ii 个元素为 aia_i.

每次随机取出一个有序数对 (x,y)(xy)(x, y)(x\neq y),然后以 sx,ys_{x, y} 的成功概率将 axa_x 变为 aya_y(若不成功则不变)。

要让全部元素都相同,求期望次数。

1ai261\leq a_i \leq26n2×103n\leq 2\times 10^3.

对于所有数据,满足 (a=1nb=1nsa,b)=n2(\sum\limits_{a=1}^{n}\sum\limits_{b=1}^n s_{a, b})=n^2.

分析#

高斯消元?看不出来有方程。

由于 (a=1nb=1nsa,b)=n2(\sum\limits_{a=1}^{n}\sum\limits_{b=1}^n s_{a, b})=n^2 ,显然每个 si,js_{i, j} 都恒为 11。这表明对于选出的任意两个数 x,yx, y每次操作都能成功

然而这并没有使这题与概率无关,因为 x,yx, y 的选取仍然是随机的。

考虑到最后全部的元素会相同,但不知道都变成什么元素,因此我们先枚举目标元素**(最后剩下的元素)。

下面开始推式子。

概率#

对序列的每次操作,可能会使目标元素的个数增加:1/0/1-1/0/1 个。

可以发现,一种元素最后成为目标元素的概率,取决于一开始序列中有多少个这种元素

因此,下面的分析建立在已经确定目标元素的基础上,换言之,只关注有多少个目标元素。

p[i]p[i] 表示:此时有 ii 个目标元素,全部元素都变成当前选取的目标元素的概率。

那么:

p[i]=i(ni)n(n1)p[i1]+i(ni)n(n1)p[i+1]+(12i(ni)n(n1))p[i]p[i]=\frac{i(n-i)}{n(n-1)}p[i-1]+\frac{i(n-i)}{n(n-1)}p[i+1]+(1-2\frac{i(n-i)}{n(n-1)})p[i]

方程代表:从有 ii 个目标元素的状态为基础,操作一次,产生的三种可能情况。

要让目标元素个数 ±1\pm 1,就要从 ii 个数中选一个,再从其他的 nin-i 个数中选一个。

nn 个数中随机选取两个的方案数是 n(n1)n(n-1),因此目标元素个数 ±1\pm 1 的概率均为 i(ni)n(n1)\frac{i(n-i)}{n(n-1)}.

而目标元素个数不变的概率,就是 11 减去前两种情况剩下的概率。

对方程进行整理,设 k=i(n1)n(n1)k=\frac{i(n-1)}{n(n-1)},那么有:

p[i]=kp[i1]+kp[i+1]+(12k)p[i]2kp[i]=kp[i1]+kp[i+1]\begin{aligned} p[i] &= k\cdot p[i-1]+k\cdot p[i+1]+(1-2k)p[i]\\ 2k\cdot p[i] &= k\cdot p[i-1]+k\cdot p[i+1]\\ \end{aligned}

kk 约掉:

2p[i]=p[i1]+p[i+1]2p[i]=p[i-1]+p[i+1]

也就是:

p[i+1]p[i]=p[i]p[i1]p[i+1]-p[i]=p[i]-p[i-1]

这说明 pp 的相邻两项差值相同,pp 是等差数列

又因为显然, p[0]=0p[0]=0p[n]=1p[n]=1,可以得到一个非常简单的形式:

p[i]=inp[i]=\frac{i}{n}

在此基础上,我们继续研究期望。

期望#

期望的分析要简单一些,设 f[i]f[i] 表示:当前有 ii 个目标元素,使全部元素都变成目标元素的期望步数

我们只关心能使目标元素个数变化的操作。可以发现,目标元素变化(+1/1+1/-1)的概率为 2i(ni)n(n1)2\frac{i(n-i)}{n(n-1)}.

因此,操作一次,期望变化 2i(ni)n(n1)\frac{2i(n-i)}{n(n-1)};变化为 11,期望次数就是 n(n1)2i(ni)\frac{n(n-1)}{2i(n-i)}.

所以得到和期望相关的方程:

p[i]f[i]=p[i]n(n1)2i(ni)+12p[i1]f[i1]+12p[i+1]f[i+1]p[i]f[i]=p[i]\cdot\frac{n(n-1)}{2i(n - i)} + \frac{1}{2}p[i - 1]f[i - 1]+\frac{1}{2}p[i + 1]f[i + 1]

方程整体的含义是:当前状态下有 ii 个目标元素,要让个数变化 11。第一项就是变化 11期望操作次数,后两项分别是个数 +1/1+1/-1 后的情况。

f[i]f[i] 乘上了 p[i]p[i],因为 p[i]p[i] 的概率最终能移动到目标元素,f[i]f[i] 的期望贡献只有 p[i]×f[i]p[i]\times f[i]

由于 +1/1+1/-1 的概率相同,所以两种情况的系数都是 12\frac{1}{2}.

同理,期望操作也要乘上 p[i]p[i],因为只有 p[i]p[i] 的概率,会向目标方向转移。(或者也可以把左边的 p[i]p[i] 除过去,用另一种方法理解)

p[i]=inp[i]=\frac{i}{n} 代入,整理得到:

f[i]=n(n1)2i(ni)+i12if[i1]+i+12if[i+1]f[i]i12if[i1]i+12if[i+1]=n(n1)2i(ni)\begin{aligned} f[i] &= \frac{n(n-1)}{2i(n-i)}+\frac{i-1}{2i}f[i-1]+\frac{i+1}{2i}f[i+1]\\ f[i]-\frac{i-1}{2i}f[i-1]-\frac{i+1}{2i}f[i+1]&= \frac{n(n-1)}{2i(n-i)} \end{aligned}

又因为 f[n]=0f[n]=0i[1,n1]i\in[1, n-1].

所以现在得到了 n1n-1 个方程,n1n-1 个未知数,可以高斯消元解方程组。

然而本题数据范围 n2×103n\leq 2\times 10^3O(n3)\mathcal{O}(n^3) 的高斯消元不可行,下面是一些消元技巧。

高斯消元#

每个方程只有三项是有系数的,矩阵长这样:

012341101a0020b1c0300d1f4000g1\begin{matrix} &&\textcolor{gray}{0} &\textcolor{gray}{1} &\textcolor{gray}{2} &\textcolor{gray}{3} &\textcolor{gray}{4}\\ \textcolor{white}{1}\\ \textcolor{gray}{1} &&0 &1 &a &0 &0\\ \textcolor{gray}{2} &&0 &b &1 &c &0\\ \textcolor{gray}{3} &&0 &0 &d &1 &f\\ \textcolor{gray}{4} &&0 &0 &0 &g &1 \end{matrix}

可以发现,对第 ii 个元,只有i1i-1iii+1i+1 行的系数非零。所以如果用高斯-约旦消元法,只需要消第 i1i-1i+1i+1 行的就行了。

又因为每行只有三个变元的系数和一个等号右边的系数,所以这样的时间复杂度是 O(n)\mathcal{O}(n).

此外,空间复杂度也能比较轻易地优化到 O(n)\mathcal{O}(n),可以参考其他题解。

代码#

Ubuntu Pastebin

概率都是 1.001.00,可以不用输入。注意数组要用 double

参考资料#

a_forever_dream的博客 - CSDN博客

P3232 - [HNOI2013]游走#

题意#

给定一个 nn 个点 mm 条边的无向连通图,顶点从 11 编号到 nn,边从 11 编号到 mm

小 Z 在该图上进行随机游走,初始时小 Z 在 11 号顶点,每一步小 Z 以相等的概率随机选择当前顶点的某条边,沿着这条边走到下一个顶点,获得等于这条边的编号的分数。当小 Z 到达 nn 号顶点时游走结束,总分为所有获得的分数之和。

现在,请你对这 mm 条边进行编号,使得小 Z 获得的总分的期望值最小

求最小期望值。

2n5002\leq n\leq 5001m1250001\leq m\leq 125000,无重边无自环。

分析#

设边 (i,j)(i,j) 的期望经过次数为 E(i,j)E_{(i, j)},答案即 E(i,j)v(i,j)\sum E_{(i, j)} v_{(i, j)},因为每条边的边权 vv 可以自己定,容易想到贪心。

先算出期望经过次数,然后让经过次数最大的边的编号最小。就是一个期望DP。

期望DP#

由于 mm 比较大,并且我们不会列出只和 EE 相关的方程式,所以先处理点。

fif_i 表示从 11 走到 nn 的过程中,经过点 ii 的期望次数。 用其表示 EE

E(i,j)=[in]fidegi+[jn]fjdegjE_{(i,j)}=[i\neq n]\frac{f_i}{\operatorname{deg_i}} + [j\neq n]\frac{f_j}{\operatorname{deg}_j}

经过一条边有两种可能的方向,分别考虑从 iji\rightarrow jjij\rightarrow i

首先根据题意要求起点不能为 nn,从 ii 出发,下一次恰好走到 jj 的概率即为 1degi\dfrac{1}{\operatorname{deg}_i}

下面计算 fif_i,和上面类似地,有:

fi={(i,j)E,jnfjdegj+1,i=1(i,j)E,jnfjdegj,i1f_i= \left\{ \begin{array}{lr} \sum_{(i, j)\in E,j\neq n}\frac{f_j}{\operatorname{deg}_j}+1, &i = 1\\ \sum_{(i, j)\in E,j\neq n}\frac{f_j}{\operatorname{deg}_j}, &i\neq1\\ \end{array} \right.

相当于对于每个 ii,枚举上一个经过的点 jj,从 jj 走到 ii 的概率为 1degj\dfrac{1}{\operatorname{deg}_j}.

对于节点 11,初始时一定会经过一次,所以 fif_i 要多 11.

对于节点 nn,走到之后就不能出去了,所以枚举 jjjnj\neq n.

于是我们得到了 nn 个有关 ff 的方程,转移有后效性,用高斯消元求解即可。

时间复杂度:O(n3)\mathcal{O}(n^3).

代码#

Ubuntu Pastebin

模型#

这种模型在概率期望题中很常见,只不过表达形式有所不同。

给定若干个点,点 uuPu,vP_{u, v} 的概率通向点 vv。终点或某些其他点,进去之后就不能出来。

求从点 SS 出发,走到点 TT 的期望步数。

这个图如果不是 DAG\rm{DAG} 的话就不能用 DP 解决,只能列方程,用高斯消元。

EiE_i 表示从 ii 走到 TT 的期望步数,方程形如:

Ei={j=1nEjPi,j+1,iT0,i=TE_i= \left\{ \begin{array}{lr} \sum_{j=1}^n E_jP_{i, j} + 1, &i\neq T\\ 0, &i=T \end{array} \right.

就是所有后继状态的期望步数乘上对应转移概率,再求和。

这道题是算期望经过次数,不是步数,所以转移略有不同。后面的题大多都是这种模型。

参考资料#

治疗之雨题解 - shadowice1984 的博客

CF24D - Broken robot#

题意#

nnmm 列的矩阵,(1,1)(1,1) 是左上角,(n,m)(n,m) 是右下角。

初始状态在 (x,y)(x,y)。每次等概率向走或原地不动,但不能走出去。

**等概率:**若其位于第一列,只能向右、下走或原地不动,则选择这三种的概率均为 13\frac{1}{3}.

求走到最后一行期望的步数,保留四位小数。

1n,m1031\leq n, m\leq 10^3.

分析#

fi,jf_{i,j} 表示从位置 (i,j)(i,j) 走到最后一行,所需行动次数的期望值。目标是要求出 fx,yf_{x, y}.

首先,初始状态为:j[1,m],fn,j=0\forall j\in[1,m],f_{n,j}=0

不在最后一行时:

fi,j={13(fi,j+fi,j+1+fi+1,j)+1,j=114(fi,j+fi,j1+fi,j+1+fi+1,j)+1,1<j<m13(fi,j+fi,j1+fi+1,j)+1,j=mf_{i, j}= \left\{ \begin{array}{lc} \frac{1}{3}(f_{i,j}+f_{i,j+1}+f_{i+1,j})+1, &j=1\\ \frac{1}{4}(f_{i,j}+f_{i,j-1}+f_{i,j+1}+f_{i+1,j})+1, &1 < j < m\\ \frac{1}{3}(f_{i,j}+f_{i,j-1}+f_{i+1,j})+1, &j=m\\ \end{array} \right.

在第一列不能再向左走,在第 mm 列不能再向右走。

暴力高斯消元,时间复杂度 O(n6)\mathcal{O}(n^6),不能接受。空间复杂度 O(n4)\mathcal{O}(n^4),更不能接受。

消元#

显然第 ii 行的状态只与 iii+1i+1 行有关,在行维度上,其满足无后效性。我们还知道 fn=0f_n=0,所以从下往上逐行计算

于是方程中 fi+1,jf_{i+1,j} 是已知的常数,直接移到等式右侧,得到方程组:

{23fi,j+13fi,j+1=13fi+1,j1,j=134fi,j+14fi,j1+14fi,j+1=14fi+1,j1,1<j<m23fi,j+13fi,j1=13fi+1,j1,j=m\left\{\begin{array}{cc} -\frac{2}{3}f_{i,j} + \frac{1}{3}f_{i,j+1} = -\frac{1}{3}f_{i+1,j}-1, &j=1\\ -\frac{3}{4}f_{i,j} + \frac{1}{4}f_{i,j-1} + \frac{1}{4}f_{i,j+1} = -\frac{1}{4}f_{i+1,j} - 1, &1<j<m\\ -\frac{2}{3}f_{i,j} + \frac{1}{3}f_{i,j-1} = -\frac{1}{3}f_{i+1,j}-1, &j=m\\ \end{array}\right.

这样的复杂度变为 O(n4)\mathcal{O}(n^4)

再观察,其系数矩阵只有主对角线附近有数,其余部分均为 00,比如 m=5m=5 时:

[23130001434140001434140001434140001323]=[13fi+1,j114fi+1,j114fi+1,j114fi+1,j113fi+1,j1]\begin{bmatrix} -\frac{2}{3} & \frac{1}{3} & 0 & 0 & 0\\ \frac{1}{4} & -\frac{3}{4} & \frac{1}{4} & 0 & 0\\ 0 & \frac{1}{4} & -\frac{3}{4} & \frac{1}{4} & 0\\ 0 & 0 & \frac{1}{4} & -\frac{3}{4} & \frac{1}{4}\\ 0 & 0 & 0 & \frac{1}{3} & -\frac{2}{3}\\ \end{bmatrix}=\begin{bmatrix} -\frac{1}{3}f_{i+1,j}-1\\ -\frac{1}{4}f_{i+1,j}-1\\ -\frac{1}{4}f_{i+1,j}-1\\ -\frac{1}{4}f_{i+1,j}-1\\ -\frac{1}{3}f_{i+1,j}-1\\ \end{bmatrix}

可以用 BandMatrix\rm{Band-Matrix} 继续优化。

这道题中,带的宽度可以看成常数。时间复杂度为 O(nm)\mathcal{O}(nm),空间复杂度为 O(m2)\mathcal{O}(m^2)O(m)\mathcal{O}(m).

代码#

我的代码只保留了每行有用的三列:Ubuntu Pastebin,下面参考资料中的代码保留了整个矩阵。

参考资料#

CF24D Broken robot - maoyiting’s blog - 洛谷博客

CF963E - Circles of Waiting#

题意#

在平面直角坐标系上,有一个神奇的点,一开始在 (0,0)(0, 0)

每秒钟这个点都会随机移动,如果它目前在 (x,y)(x, y),那么下一秒它:

  • (x1,y)(x - 1, y) 的概率是 p1p_1
  • (x,y1)(x, y - 1) 的概率是 p2p_2
  • (x+1,y)(x + 1, y) 的概率是 p3p_3
  • (x,y+1)(x, y + 1) 的概率是 p4p_4.

保证 p1+p2+p3+p4=1p_1 + p_2 + p_3 + p_4 = 1,各次移动互不关联。

求出这个点移动到距离原点距离大于 RR 的点的期望步数,对 109+710^9+7 取模。

欧几里得距离,0R500\leq R \leq 50

分析#

先理解一下题意,下面是 CF官方题解 里的图。

只能在整点上移动,移出半径为 RR 的圆为止。

期望DP,设 fx,yf_{x, y} 为:从 (x,y)(x,y) 走出圆所需要的期望步数,方程显然:

fx,y={fx1,yp1+fx,y1p2+fx+1,yp3+fx,y+1p4+1,x2+y2R0,x2+y2>Rf_{x, y}= \left\{ \begin{array}{lc} f_{x-1, y}p_1+f_{x, y-1}p_2+f_{x+1, y}p_3+f_{x, y+1}p_4+1, &\sqrt{x^2+y^2}\leq R\\ 0, &\sqrt{x^2+y^2}> R \end{array} \right.

高斯消元直接解的话,点数是略小于 4R24R^2,时间复杂度是 O(R6)\mathcal{O}(R^6).

考虑利用 BandMatrix\rm{Band-Matrix} 优化。

如果我们对圆内的点,从左到右,从上到下进行编号。若 id(x,y)=x\operatorname{id}(x, y)=x,则其四周点的 id[x2R,x+2R]\operatorname{id}\in[x-2R, x+2R].

因此,第 xx 列,非零的范围是 [x2R,x+2R][x-2R, x+2R],矩阵是宽度为 4R4R 的带状矩阵,

消元时只需要考虑向下的 2R2R 列和向右的 2R2R 列,消成倒三角矩阵,再回代即可。

这样的时间复杂度是 O(R4)\mathcal{O}(R^4) 的。未知数个数是 R2R^2 级别的,消元是 R×RR\times R 级别的。

代码#

代码实现上,注意坐标可能为负,细节比较多。

Ubuntu Pastebin

参考资料#

这篇比洛谷题解详细:UnnamedOrange - CSDN博客

线性代数入门#

这部分差的有点多,赛后再补。

线性变换与矩阵#

线性代数的本质 - 03 - 矩阵与线性变换

线性代数的本质 - 04 - 矩阵乘法与线性变换复合

矩阵求逆#

求一个 N×NN\times N 的矩阵的逆矩阵。答案对 109+7{10}^9+7 取模。

算法思想#

从线性变换的角度理解矩阵和逆矩阵。

逆矩阵表示:把原矩阵变到单位矩阵这一线性变换过程。因此,我们通过高斯消元把原矩阵变换为单位矩阵,同时新建一个矩阵记录变换过程。

算法流程#

AA 是原矩阵,A1A^{-1}AA 的逆矩阵,则 AA1=EnA\cdot A^{-1}=E_nEnE_n 代表 nn 阶单位矩阵。

E=[10000100100100001]E=\begin{bmatrix} 1 &0 &\cdots &0 &0 \\ 0 &1 &\cdots &0 &0 \\ \cdots &\cdots &1 &\cdots &\cdots \\ 0 &0 &\cdots &1 &0 \\ 0 &0 &\cdots &0 &1 \end{bmatrix}

若 我们把一个单位矩阵 EnE_n 并在 AA 的右边,之后通过高斯-约旦消元法,把 AA 消元成 EnE_n 的形式,此时 BB 就是矩阵 AA 的逆。

**具体地,**以 n=5n=5 为例,把两个矩阵合到一起:

[AEn]=[a1,1a1,2a1,3a1,4a1,5a2,1a2,2a2,3a2,4a2,5a3,1a3,2a3,3a3,4a3,5a4,1a4,2a4,3a4,4a4,5a5,1a5,2a5,3a5,4a5,5|11111]\begin{bmatrix} A & E_n \end{bmatrix}= \left[ \begin{matrix} a_{1, 1} &a_{1, 2} &a_{1, 3} &a_{1, 4} &a_{1, 5} \\ a_{2, 1} &a_{2, 2} &a_{2, 3} &a_{2, 4} &a_{2, 5} \\ a_{3, 1} &a_{3, 2} &a_{3, 3} &a_{3, 4} &a_{3, 5} \\ a_{4, 1} &a_{4, 2} &a_{4, 3} &a_{4, 4} &a_{4, 5} \\ a_{5, 1} &a_{5, 2} &a_{5, 3} &a_{5, 4} &a_{5, 5} \end{matrix} \middle| \begin{matrix} 1 & & & & \\ &1 & & & \\ & &1 & & \\ & & &1 & \\ & & & &1 \end{matrix} \right]

通过高斯消元,把左半部分矩阵变成单位矩阵:

[EnA1]=[11111|b1,1b1,2b1,3b1,4b1,5b2,1b2,2b2,3b2,4b2,5b3,1b3,2b3,3b3,4b3,5b4,1b4,2b4,3b4,4b4,5b5,1b5,2b5,3b5,4b5,5]\begin{bmatrix} E_n & A^{-1} \end{bmatrix}= \left[ \begin{matrix} 1 & & & & \\ &1 & & & \\ & &1 & & \\ & & &1 & \\ & & & &1 \end{matrix} \middle| \begin{matrix} b_{1, 1} &b_{1, 2} &b_{1, 3} &b_{1, 4} &b_{1, 5} \\ b_{2, 1} &b_{2, 2} &b_{2, 3} &b_{2, 4} &b_{2, 5} \\ b_{3, 1} &b_{3, 2} &b_{3, 3} &b_{3, 4} &b_{3, 5} \\ b_{4, 1} &b_{4, 2} &b_{4, 3} &b_{4, 4} &b_{4, 5} \\ b_{5, 1} &b_{5, 2} &b_{5, 3} &b_{5, 4} &b_{5, 5} \end{matrix} \right]

此时矩阵 B=A1B=A^{-1}.

行列式#

给定一个 nn 阶行列式 AA,求 A|A|。结果对 pp 取模。

行列式#

对于一个 nn 阶矩阵,有:

detA=p(1)τ(p)i=1nai,pi\operatorname{det}A=\sum\limits_{p}(-1)^{\tau(p)}\prod\limits_{i=1}^na_{i,p_i} ,其中 pp 是任意一个排列,τ(p)\tau(p) 指的是 pp 中的逆序对数。但这个定义暂时没啥用。

矩阵 AA 的行列式可以表达为 A|A|.

性质#

建议先看 3b1b 的视频:线性代数的本质 - 05 - 行列式,再看下面的性质。

以下内容参考 MIT公开课 - 行列式笔记线性代数公开课 - MIT 18.06 Linear Algebra - 系列合集 - 哔哩哔哩.

性质 11#

E=1|E|=1EE 为单位矩阵。直接用代数定义就可以证明。

性质 22#

**交换矩阵的两行,行列式的值变为相反数。**也直接用代数方法证明。

性质 3.13.1#

矩阵中的一行乘系数 tt,等价于整个行列式乘系数 tt,即:

ta1a2a3b1b2b3c1c2c3=ta1ta2ta3b1b2b3c1c2c3t\cdot \left|\begin{array}{ccc} a_1 & a_2 & a_3\\ b_1 & b_2 & b_3\\ c_1 & c_2 & c_3\\ \end{array}\right| = \left|\begin{array}{ccc} t\cdot a_1 & t\cdot a_2 & t\cdot a_3\\ b_1 & b_2 & b_3\\ c_1 & c_2 & c_3\\ \end{array}\right|

性质 3.23.2#

对于每一行而言,行列式是线性的,即:

a1a2a3b1b2b3c1c2c3+d1d2d3b1b2b3c1c2c3=a1+d1a2+d2a3+d3b1b2b3c1c2c3\left|\begin{array}{ccc} a_1 & a_2 & a_3\\ b_1 & b_2 & b_3\\ c_1 & c_2 & c_3\\ \end{array}\right| + \left|\begin{array}{ccc} d_1 & d_2 & d_3\\ b_1 & b_2 & b_3\\ c_1 & c_2 & c_3\\ \end{array}\right| = \left|\begin{array}{ccc} a_1+d_1 & a_2+d_2 & a_3+d_3\\ b_1 & b_2 & b_3\\ c_1 & c_2 & c_3\\ \end{array}\right|

注意 A+BA+B|A+B|\neq|A|+|B|.

性质 44#

若存在两行相等,则行列式为 00若存在两行成倍数关系,则行列式为 00.

证明:交换这两行,行列式值变号,又因为行列式值不变,所以 det=0\operatorname{det}=0.

性质 55#

**消元不影响行列式的值。**消元:第 ii 行减去第 jj 行的若干倍。

a1a2a3b1b2b3c1ka1c2ka2c3ka3Theory 3.2a1a2a3b1b2b3c1a1c2c3+a1a2a3b1b2b3ka1ka2ka3Theory 4a1a2a3b1b2b3c1a1c2c3+0\begin{array}{cl} &\left|\begin{array}{ccc} a_1 & a_2 & a_3\\ b_1 & b_2 & b_3\\ c_1 - k\cdot a_1& c_2 - k\cdot a_2& c_3-k\cdot a_3\\ \end{array}\right|\\ \underset{\text{Theory 3.2}}{\longrightarrow}& \left|\begin{array}{ccc} a_1 & a_2 & a_3\\ b_1 & b_2 & b_3\\ c_1\cdot a_1& c_2& c_3\\ \end{array}\right| + \left|\begin{array}{ccc} a_1 & a_2 & a_3\\ b_1 & b_2 & b_3\\ -k\cdot a_1& - k\cdot a_2 &-k\cdot a_3\\ \end{array}\right|\\ \underset{\text{Theory 4}}{\longrightarrow}& \left|\begin{array}{ccc} a_1 & a_2 & a_3\\ b_1 & b_2 & b_3\\ c_1\cdot a_1& c_2& c_3\\ \end{array}\right| + 0 \end{array}

性质 66#

若行列式有一整行为 00,则行列式为 00

是性质 33 中的 t=0t=0 的特殊情况。

性质 77#

倒三角矩阵行列式的值,等于对角线元素的乘积,所以只与对角线上的数有关。

xxxxxxEliminatexxx\left|\begin{array}{ccc} x & x & x\\ & x & x\\ & & x\\ \end{array}\right| \underset{\text{Eliminate}}{\longrightarrow} \left|\begin{array}{ccc} x & & \\ & x & \\ & & x\\ \end{array}\right|

形如上面的矩阵,通过消元转化为更简形式即可。

求解#

根据性质 55 和性质 77,直接高斯消元即可。注意每次交换行时,需要AnsAns\operatorname{Ans}\leftarrow -\operatorname{Ans}.

int Gauss() {
	int det = 1;
	for(int col = 1; col <= n; col++) {
		for(int row = col + 1; row <= n; row++) {
			while(a[row][col]) {
				int t = a[col][col] / a[row][col];
				for(int i = 1; i <= n; i++) {
					a[col][i] = (a[col][i] + a[row][i] * (p - t)) % p;
                //  a[col][i] = (a[col][i] - a[row][i] * t      ) % p;
				}
				for(int i = 1; i <= n; i++) swap(a[col][i], a[row][i]);
				det *= -1;
			}
		}
	}
	for(int i = 1; i <= n; i++) det = det * a[i][i] % p;
    return (det + p) % p;
}
cpp

LGV引理#

LGV引理#

参考 OI Wiki,OIer 不需要证明,能完全理解定理内容就不错了。

定义如下#

  • GG,一张带有边权的有向无环图
  • vev_e, 边 ee边权
  • ω(P)\omega(P),对于一个依次经过 u1,u2,uku_1,u_2\cdots, u_k 的有向路径 P(u1uk)P(u_1\rightsquigarrow u_k)ω(P)\omega(P) 表示其所有边权之积
  • e(u,v)=P:uvω(P)e(u, v)=\sum\limits_{P: u\rightsquigarrow v}\omega(P),表示 uuvv每一条路径 PPω(P)\omega(P) 之和
  • A={a1,a2,an}A=\{a_1, a_2,\cdots a_n\}起点集合B={b1,b2,bn}B=\{b_1, b_2,\cdots b_n\},终点集合。
  • S={S1,S2,,Sn}S=\{S_1,S_2,\cdots,S_n\},是路径的集合,代表 nnABA\rightarrow B不相交路径。形式化地,可以用一个排列 σ(S)\sigma(S) 来表示,使得 SiS_i 代表路径 AiBσ(S)iA_i\rightsquigarrow B_{\sigma(S)_i},这组路径满足:ij\forall i\neq jSiS_iSjS_j 无公共点。
  • N(σ(S))N(\sigma(S)),表示排列 σ(S)\sigma(S) 的逆序对个数。

引理#

对于矩阵:

M=[e(A1,B1)e(A1,B2)e(A1,Bn)e(A2,B1)e(A2,B2)e(A2,Bn)e(An,B1)e(An,B2)e(An,Bn)]M = \begin{bmatrix}e(A_1,B_1)&e(A_1,B_2)&\cdots&e(A_1,B_n)\\ e(A_2,B_1)&e(A_2,B_2)&\cdots&e(A_2,B_n)\\ \vdots&\vdots&\ddots&\vdots\\ e(A_n,B_1)&e(A_n,B_2)&\cdots&e(A_n,B_n) \end{bmatrix}

其行列式满足:

det(M)=S:AB(1)N(σ(S))i=1nω(Si)\det(M)=\sum\limits_{S:A\rightarrow B}(-1)^{N(\sigma(S))}\prod\limits_{i=1}^n \omega(S_i)

其中 S:ABS:A\rightarrow B 代表:枚举每一组路径(满足上文要求的)。

证明略,下面两道题会说到 LGV 引理的应用方法,

模板题#

题意#

有一个 n×nn\times n 的棋盘,左下角为 (1,1)(1,1),右上角为 (n,n)(n,n),若一个棋子在点 (x,y)(x,y),那么走一步只能走到 (x+1,y)(x+1,y)(x,y+1)(x,y+1)

现在有 mm 个棋子,第 ii 个棋子一开始放在 (ai,1)(a_i,1),最终要走到 (bi,n)(b_i,n)。问有多少种方案,使得每个棋子都能从起点走到终点,且对于所有棋子,走过路径上的点互不相交。输出方案数 mod 998244353\bmod\ 998244353 的值。

两种方案不同当且仅当存在至少一个棋子所经过的点不同。

分析#

方格图,每次只能向下或向右,不妨设 aia_ibib_i 均为单调上升的。

因为引理要求路径不相交,所以唯一σ(S)=(1,2,,n)\sigma(S)=(1, 2,\cdots, n)N(σ(S))=0N(\sigma(S))=0,也就是 aia_ibib_i 配对。

设每条边的边权均为 11,则 ω(e)=1\forall \omega(e)=1,所以:

det(M)=S:AB(1)N(σ(S))i=1nω(Si)=S:AB(1)0=S:AB1\det(M)=\sum\limits_{S:A\rightarrow B}(-1)^{N(\sigma(S))}\prod\limits_{i=1}^n \omega(S_i)=\sum\limits_{S:A\rightarrow B}(-1)^0=\sum\limits_{S:A\rightarrow B}1

右式即为题目所求。

构造矩阵,e(ai,bj)=(bjai+n1n1)e(a_i,b_j)=\binom{b_j-a_i+n-1}{n-1},高斯消元求其行列式即可。

[NOI2021] 路径交点#

题面 太长就不放了。

同样地,设每条边的边权均为 11,则 ω(e)=1\forall \omega(e)=1,所以:

det(M)=S:AB(1)N(σ(S))i=1nω(Si)=S:AB(1)N(σ(S))\det(M)=\sum\limits_{S:A\rightarrow B}(-1)^{N(\sigma(S))}\prod\limits_{i=1}^n \omega(S_i)=\sum\limits_{S:A\rightarrow B}(-1)^{N(\sigma(S))}

引理:两条路径交点个数的奇偶性,与将其起点和终点直接相连的交点个数的奇偶性相同。

因此,若我们用 LGV 引理的 σ(S)\sigma(S) 表示一组路径,若 N(σ(S))mod2=0N(\sigma(S))\bmod 2=0,则有路径偶数个交点,反之亦然。

本题求:偶数个交点的方案数量 - 奇数个交点的方案数量,这恰好对应式子中的 (1)N(σ(S))(-1)^{N(\sigma(S))}.

因此,直接构造矩阵 MM 并求行列式,e(i,j)e(i, j) 表示从第一层 ii 点到最后一层 jj 点的路径数量。

求路径数量可以DP,或者记忆化搜索,不是这道题的难点。

参考资料:NOI 2021 D1T2 题解 - ix35_ 的博客 - 洛谷博客

Matrix-Tree 定理#

基尔霍夫矩阵#

基尔霍夫矩阵,又称拉普拉斯矩阵,用于描述一张图的某些属性。

无向图#

给定一张简单无向图 GG,其包含 nn 个顶点 v1,v2,vnv_1, v_2\cdots, v_n,其拉普拉斯矩阵 LL 为:

Li,j={deg(vi)\mboxif i=j1\mboxif ij \mboxand vi\mboxisadjacenttovj0\mboxotherwiseL_{i,j}= {\begin{cases}\deg(v_{i})&{\mbox{if}}\ i=j\\-1&{\mbox{if}}\ i\neq j\ {\mbox{and}}\ v_{i}{\mbox{ is adjacent to }}v_{j}\\0&{\mbox{otherwise}}\end{cases}}

也就是说,若设 DD 为图的度数矩阵,AA 为图的邻接矩阵,则等价地有:

L=DAL=D-A

下面是一个对于无向图的例子:

度数矩阵邻接矩阵基尔霍夫矩阵
(200000030000002000000300000030000001){\textstyle \left({\begin{array}{rrrrrr}2&0&0&0&0&0\\0&3&0&0&0&0\\0&0&2&0&0&0\\0&0&0&3&0&0\\0&0&0&0&3&0\\0&0&0&0&0&1\\\end{array}}\right)}(010010101010010100001011110100000100){\textstyle \left({\begin{array}{rrrrrr}0&1&0&0&1&0\\1&0&1&0&1&0\\0&1&0&1&0&0\\0&0&1&0&1&1\\1&1&0&1&0&0\\0&0&0&1&0&0\\\end{array}}\right)}(210010131010012100001311110130000101){\textstyle \left({\begin{array}{rrrrrr}2&-1&0&0&-1&0\\-1&3&-1&0&-1&0\\0&-1&2&-1&0&0\\0&0&-1&3&-1&-1\\-1&-1&0&-1&3&0\\0&0&0&-1&0&1\\\end{array}}\right)}

有向图#

有向图存在两个基尔霍夫矩阵,分别为出度基尔霍夫矩阵 LoutL^\text{out}入度基尔霍夫矩阵 LinL^\text{in}.

其含义与无向图类似,设图 GG 的出度矩阵为 DoutD^\text{out},入度矩阵为 DinD^\text{in},则有:

Lout=DoutALin=DinAL^\text{out}=D^\text{out}-A\\ L^\text{in}=D^\text{in}-A

定理内容#

规定:两个生成树不同,当且仅当存在一条边的被包含情况不同。

定理仅适用于无重边、无自环的情况。

无向图#

t(G)t(G) 为无向连通图 GG 的生成树个数。

LL 为无向连通图 GG 的基尔霍夫矩阵,任选 ii,从 LL 中删去第 ii 行和第 ii 列,构造出矩阵 LL^{\prime},则:

t(G)=detLt(G)=\det L^{\prime}

如图 GG 的基尔霍夫矩阵为 L=[2110131111310112]L=\left[{\begin{array}{rrrr}2&-1&-1&0\\-1&3&-1&-1\\-1&-1&3&-1\\0&-1&-1&2\end{array}}\right],则 LL^{\prime} 可以为 L=[2110131111310112]L^{\prime}=\left[{\begin{array}{rrrr}2&-1&-1&0\\-1&3&-1&-1\\-1&-1&3&-1\\0&-1&-1&2\end{array}}\right].

有向图#

当根固定时,有向图的生成树分为根向生成树叶向生成树,顾名思义,根向生成树的每条边都指向根叶向生成树的每条边都指向根

troot(G,s)t^\text{root}(G,s) 为以 ss 为根节点的根向生成树个数, tleaf(G,s)t^\text{leaf}(G,s) 为以 ss 为根节点的叶向生成树个数。

LoutL^\text{out}LinL^\text{in} 为有向连通图 GG 的基尔霍夫矩阵,从中删去第 kk 行第 kk 列后的矩阵为 Lout(k){L^\text{out}}^{\prime}(k)Lin(k){L^\text{in}}^{\prime}(k).

钦定 kk 为所统计生成树的根节点,则有:

troot(G,s)=detLout(s)t^\text{root}(G,s)=\det{L^\text{out}}^{\prime}(s)\\ tleaf(G,s)=detLin(s)t^\text{leaf}(G,s)=\det{L^\text{in}}^{\prime}(s)\\

所以,有向图的根向树形图个数为 utroot(G,u)\sum_{u} t^\text{root}(G,u),叶向同理

简要证明#

参见 Kirchhoff’s theorem - Proof outline - Wikipedia.

参考资料#

Laplacian matrix - WikipediaKirchhoff’s theorem - Wikipedia.

还有例题:[HEOI2015] 小Z的房间.

一些资料#

博弈论基础
https://www.tonyyin.top/blog/oi-notes/gauss-elimination
Author TonyYin
Published at October 16, 2021
Comment seems to stuck. Try to refresh?✨