线性代数
1. 矩阵与向量
1.1 定义
- 矩阵:\(n\) 行 \(m\) 列的方阵就是一个 \(n\times m\) 的矩阵,通常用大写字母表示。特别的,当 \(n=m\) 时,称这个矩阵为 \(n\) 阶方阵。
- 向量:\(1\) 行 \(m\) 列的矩阵称为行向量,\(m\) 行 \(1\) 列的矩阵称为列向量。下文若无特殊说明,均为列向量,用 \(\vec a\) 表示。
描述矩阵第 \(i\) 行第 \(j\) 列:\(a_{i,j}\) 或者 \(A_{i,j}\)。
描述向量第 \(i\) 个元素:\(\vec a_i\)。
- (主)对角线:在 \(n\) 阶方阵中,\(a_{1,1},a_{2,2},\cdots,a_{n,n}\) 这些左上角到右下角的元素。
- 反对角线:右上角到左下角。
- 单位矩阵:主对角线均为 \(1\) 的方阵。有性质:\(AI=A\)。
- 行阶梯形:(1) 每一非零行中首个非零元为 \(1\)。(2) 第 \(k\) 行元不全 \(0\) 时,第 \(k+1\) 行首变量之前零的个数多于第 \(k\) 行首变量之前 \(0\) 的个数。(3) 所有元素为 \(0\) 的行在不为零行之后。
1.2 运算
- 加法:适用于两个大小相同的矩阵。\(A+B=C \iff A_{i,j}+B_{i,j}=C_{i,j}\)
- 数乘:\(B=cA\iff B_{i,j}=cA_{i,j}\)
- 转置:将矩阵进行反转。\((A^T)_{j,i}=A_{i,j}\)。
- 矩阵乘法:对于大小为 \(n*m\) 与 \(m*p\) 的矩阵 \(A\)、\(B\),定义 \(C=A\times B\iff C_{i,j}=\sum\limits_{k=1}^mA_{i,k}\times B_{k,p}\),即乘法结果是一个 \(n\times p\) 的矩阵 \(C\)。
- 逆矩阵:对于方阵 \(A\),定义 \(A^{-1}\) 为其逆矩阵当 \(AA^{-1}=A^{-1}A=I\) 时。并不是所有方阵都有逆。
不懂?
换种方式理解试试。
我们把 \(A\) 矩阵看作是 \(n\) 个长度为 \(m\) 的行向量,\(B\) 矩阵看作是 \(r\) 个长度为 \(m\) 的列向量。而向量的点积形式为:\(\vec a\cdot\vec b=\sum\limits_i a_ib_i\)。于是 \(C_{i,j}\) 的值就是 \(A\) 矩阵第 \(i\) 个行向量与 \(b\) 向量第 \(j\) 个向量点积的值。
然后来点运算律?
- \((AB)^T=B^TA^T\)
算一下就好了。 - 交换律:\(A+B=B+A\)
乘法是不满足的,为啥就举个例子思考下呗。 - 结合律:\(A\oplus(B\oplus C)=(A\oplus B)\oplus C\)
对乘法加法都满足。 - 结合律:\(A\times(B+C)=A\times B+A\times C\)
1.3 应用
1.3.0 矩阵幂
下文中, 我们默认对 \(n\) 阶方阵 \(A\) 进行 \(k\) 次幂运算, 且代码中出现的 \(\text{Matrix}\) 类初始均为单位矩阵。
快速幂
类比实数快速求幂的方法, 在 \(\mathcal O(\log k)\) 的复杂度内计算。我们亦可用快速幂在 \(\mathcal O(n^3\log k)\) 计算一个 \(n\) 阶方阵的 \(k\) 次幂。也就是将幂次二进制分组快速计算。
cpp 代码示例如下。
Matrix qpow (Matrix base, int k) {
Matrix ret; ret.init (base.n, base.n);
// ret is a n * n identity matrix
while (k) {
if (k & 1) ret = ret * base;
base = base * base;
k >>= 1;
}
return ret;
}
光速幂
对于固定底数的矩阵运算, 可以采用分块司想。我们按照 \(B\) 进制分组, 预处理出 \(B^0\), \(B^1\), \(\cdots\), \(B^k\) 的 \(k\le B\) 倍幂, 在 \(\mathcal O(n^3\log_Bk)\) 的复杂度在线查询。预处理的复杂度为 \(\mathcal O(Bn^3\log_B k)\), 这里的 \(k\) 为询问中最大幂次。
实际上, 我们经常将光速幂用于优化 \(\mathcal{O}(n^3)\) 至 \(\mathcal{O}(n^2)\) 的运算中。我们维护一个转移矩阵 \(A\), 以及答案向量 \(f\)。我们设最终答案为 \(A^k\cdot f\)。我们需要求出 \(A^k\), 但实际上, 在求解 \(A^k\) 的过程中, 可以维护 \(f\), 这样就可以将 \(\mathcal O(n^3\log_B k)\) 的复杂度降为 \(\mathcal O(n^2\log_B k)\)。
cpp 代码示例如下。
for (int i = 2; i <= B; i ++) f[0][i] = f[0][i - 1] * f[0][1];
for (int i = 1; i <= B; i ++) f[1][i] = f[1][i - 1] * f[0][B];
for (int i = 1; i <= B; i ++) f[2][i] = f[2][i - 1] * f[1][B];
值得注意的是, 选择不同的块长, 以获得更优复杂度, 在 \(n\) 较大的题目中, 我们一般采用 \(B=2\) 作为块长。
1.3.1 矩阵加速递推
Fibonacci 数列
设 \(f_0=0,f_1=1\), \(f_n=f_{n-1}+f_{n-2}\), 求 \(f_k\) 对 \(10^9+7\) 取模的结果。其中\(k\le 10^{18}\)。
这是矩阵加速的最为经典的应用, 注意到递推过程可以写作矩阵形式:
$$\begin{bmatrix}f_{i+1} \ f_{i}\end{bmatrix} = \begin{bmatrix} 1 & 1 \ 1 & 0\end{bmatrix}\begin{bmatrix}f_i \f_{i - 1}\end{bmatrix}$$
因此, 我们只需设转移矩阵 \(A= \begin{bmatrix}1 & 1 \\ 1 & 0\end{bmatrix}\), 初始向量 \(f_0=\begin{bmatrix}1\\0\end{bmatrix}\), 那么答案即为 \((A^k\cdot f_0)_{1,1}\), 矩阵快速幂即可。
OI-Wiki 例
\(f_1=f_2=0\), \(f_n=7f_{n-1}+6f_{n-2}+5n+4\times 3^n\)。求 \(f_k\)。
同样的, 我们只需找到转移矩阵 \(A\) 以及答案向量 \(f\) 即可。
1.3.2 矩阵表达修改
P4314 CPU 监控 pushup 部分
共有 \(n\) 个数, \(q\) 次操作。每次操作可以进行区间加, 区间修改操作。也可以查询区间查询历史以及当前最值。\(n,q\le 10^5\)。
直接做需要进行大量的分类讨论, 但借助矩阵转移可以大大降低思维难度。我们定义类矩阵乘法:
对于序列中每一个数, 维护 \(a,b\) 表示当前值以及历史最值。那么区间加 \(k\) 相当于: \(\begin{bmatrix}a' \\b'\end{bmatrix}=\begin{bmatrix}a+k\\\max(b,a+k)\end{bmatrix}=\begin{bmatrix}k & -\infty\\k & 0\end{bmatrix}\begin{bmatrix}a\\b\end{bmatrix}\)。但是这题有修改操作, 我们只需再加一维:
最后在线段树上维护矩阵即可。
P4719 动态DP
动态查询给定树修改某个点的权值后的最大权独立集的权值大小, \(Q\le10^5\) 次。
这道题就是 P1352 一题的带修版。我们考虑这道题的转移式。设 \(f_{i,1/0}\) 表示以 \(i\) 为根, 选/不选点 \(i\) 的答案, \(a_i\) 为 \(i\) 的权值。显然有 \(f_{i,1}=a_i+\sum\limits_{u\in son_i}f_{u,0}\), \(f_{i,0}=\sum\limits_{u\in}\max\{f_{u,0},f_{u,1}\}\)。
对于这道带修版, 我们仍然设 \(f_{i,1/0}\) 表示以 \(i\) 为根, 选/不选点 \(i\) 的答案。再设 \(g_{i,1/0}\) 表示以 \(i\) 为根, 选/不选点 \(i\) 的答案(且不选重儿子)。设 \(v\) 是节点 \(i\) 的重儿子, 我们有:
如法炮制地定义类矩阵乘法:
$$A\oplus B=\max\limits_{k=1}^m a_{i,k}+b_{k,j}$$
于是我们将转移写成矩阵的形式。那么便有:
在第二个 dfs 中, 我们可以直接计算出 \(f\) 和 \(g\) 的结果, 照着上面来一遍即可。
代码中的 \(g\) 即为转移矩阵。
void dfs2 (int x, int top) {
tp[x] = top;
id[x] = ++ tim; dfn[tim] = x; ed[top] = max (ed[top], tim);
f[x][0] = 0, f[x][1] = a[x];
g[x].M[0][0] = g[x].M[0][1] = 0; g[x].M[1][0] = a[x];
if (son[x]) {
dfs (son[x], top);
f[x][0] += max (f[ son[x] ][0], f[ son[x] ][1]);
f[x][1] += f[ son[x] ][0];
}
for (auto v : G[x]) {
if (id[v]) continue;
dfs (v, v);
f[x][0] += max (f[v][0], f[v][1]);
f[x][1] += f[v][0];
g[x].M[0][0] += max (f[v][0], f[v][1]);
g[x].M[0][1] = g[x].M[0][0];
g[x].M[1][0] += f[v][0];
}
}
接下来考虑带修。
不妨设修改的点为 \(u\), 修改成的权值为 \(w\), 那么 \(g_{u,1}\) 应该加上 \(w-a_u\), 并 \(a_u\gets w\)。
和正常树剖一样, 从 \(u\) 点往上跳, 这里我们存了两个临时矩阵 \(former\) 与 \(latter\) 分别表示修改前的 \(u\) 至其链顶 的 \(f\) 矩阵及修改后的 \(f\) 矩阵。
将 \(u\) 点跳到其所在链的链顶的父亲 \(fa\), 那么根据重链的定义, 边 \((u,fa)\) 必定是一条轻边, 也就是说 \(u\) 及其子树 \(S_u\) 会对 \(g\) 做出贡献。
对于 \(g_{fa,0}\), 其余不变, 仅修改 \(S_u\) 这一子树的 \(f\)。因此我们仅需 加上变化值 即可。而状态转移式决定了我们的方式, 但稍微思考, 便知先取 \(\max\{g_{u,0},g_{u,1}\}\) 再做差。
对于 \(g_{fa,1}\), 由于不取 \(u\), 故直接用 \(latter\) 与 \(former\) 做差即可。
void path_upd (int u, int w) {
g[u].M[1][0] += w - a[u];
a[u] = w;
Matrix former, latter;
while (u) {
former = T.query (1, id[ tp[u] ], ed[ tp[u] ]);
T.upd (1, id[u]);
latter = T.query (1, id[ tp[u] ], ed[ tp[u] ]);
int fa = fa[ tp[u] ];
g[fa].M[0][0] += max (latter.M[0][0], latter.M[1][0]) -
max (former.M[0][0], former.M[1][0]);
g[fa].M[0][1] = g[fa].M[0][0];
g[fa].M[1][0] += latter.M[0][0] - former.M[0][0];
}
}
答案即为根节点最值。
NOI2020 美食家
给出有向图, 每个边 \(u\to v\) 有长度 \(w\), 表示 \(u\) 到 \(v\) 需要 \(w\) 天的时间。每去一个点都会获得愉悦值 \(a_i\), 多次经过可以多次获得。特别的, 有 \(k\) 个活动, 即第 \(i\) 天中到达 \(u\) 可以额外获得 \(j\) 的愉悦值。从 \(1\) 出发, 求恰旅游 \(T\) 天, 回到 \(1\) 的最大愉悦值。\(n\le 50,T\le10^9,w\le 5,k\le200\)。
设 \(t\) 时刻在点 \(u\) 的最大愉悦值为 \(f_{t,u}\), 那么有 \(f_{t,u}=\max\limits_{(v,u,w)\in G} f_{t-w,v}+c_u\), 若 \(t\) 时刻 \(u\) 有美食节, 加上额外贡献即可。考虑拆点, 将路径长为 \(w\) 的边拆为多个点, 那么上式即为 \(f_{t,u}=\max\limits_{(v,u)\in G} f_{t-1,v}+c_u\)。由此, 下文所写的边, 都视作 \(w=1\)。
考虑广义的矩阵乘法, 我们定义对于 \(n\times m\)、\(m\times r\) 的矩阵 \(C=A\oplus B\) 为 \(C_{ij}=\max\limits_{1\le k\le m}(A_{ik}+B_{kj})\), 这样, 我们即可用矩阵乘法的形式表示状态转移。
具体来说, 对于我们所设 \(f_t=\begin{bmatrix}f_{t1}\\f_{t2}\\\vdots\\f_{tn}\end{bmatrix}\), 根据我们定义的矩阵乘法, 考虑如何构造转移矩阵 \(G\)。当 \(u\) 可以转移到 \(v\) 时, 当且仅当存在边 \((u,v)\)。我们设 \(u\to v\) 的边权为 \(c_v\), 即 \(G_{uv}=c_v\)。若直接使 \(f_{t+1}=G\oplus f_t\), 我们发现乘积的结果意义不明, 因为 \(G_{uv}\) 的意义是 \(u\) 向 \(v\) 的结果。我们建反图即可。也就是对于所有边 \((u,v)\), 令 \(G_{vu}=c_v\), 也就是相当于将这个矩阵转置了。这样, \(G\) 的每一行也就代表着到达某一点的权了。对于 \(k=0\), 答案即为 \((G^T\oplus f_0)_{1,1}\)。
实际上, 我们令 \(G_{vu}=c_u\) 亦可。这是因为我们最后的答案形成的路径必然是一个环, 具有一定的对称性。
接下来考虑 \(k\not=0\) 的情况。其实和 \(k=0\) 异曲同工。我们只需按照时间排序, 对于每个间隔 \(delta=T-t\), 令 \(now\gets G^{delta}\oplus now\) 即可。复杂度为 \(\mathcal O((nw)^3\sum\log delta)\), 无法接受。
预处理出 \(G^1,G^2,G^4,\cdots,G^{2^{29}}\) 的结果, 最后在计算时便可实现 \(\mathcal O((nw)^2\log T)\) 的复杂度。
1.3.3 定长路径计数
AtCoder Edu.DP R
给出邻接矩阵, \(G_{ij}=1/0\) 表示存在/不存在边 \(i\to j\), 求长度为 \(K\) 的路径数。\(n\le 50,K\le 10^{18}\)。
考虑 DP。令 \(f(i,j)\) 表示长度为 \(i\) 终点为 \(j\) 的路径总数。那么有 \(f(i,j)=\sum\limits_{u=1}^n G(u,j)\times f(i-1,u)\)。
考虑降维, 即 \(f(j)=\sum\limits_{u=1}^n G(u,j)\times f_{last}(u)\), 这和矩阵乘法的形式不谋而合。那么 \(f_{ans}=G^K f_0\), \(f_0\) 初始为长度为 \(n\) 的列向量, 初值均为 \(1\)。答案即为 \(f_{ans}\) 各数之和。
[SCOI2009] 迷路
给出邻接矩阵, 边有边权 \(w\), 求 \(1\to n\) 有多少长度为 \(t\) 的路径。\(n,w\le 10,t\le 10^9\)。
我们只需拆点, 这样就会形成 \(wn\) 个点的边权为 \(1\) 的新图。这样转化为了上一题。
[SDOI2009] HH 去散步
给出 \(m\le60\) 条无向边, 不能连续经过一条边。求 \(s\to e\) 的路径数。
前两道例题我们都采用点矩阵的方式维护, 这道题我们考虑\textbf{边矩阵}。
我们只需将原图关系映射到一个由边组成的矩阵上即可实现。下面用链式前向星说明构造方法。
设 \(cnt=0\), 我们先加入 \(0\to s\) 这条边, 这是这是题设的一部分。后每次加入一条无向边 \(u\to v\), 编号相邻, 且互为与 \(1\) 异或值。我们在构造矩阵时, 只需使 \(i\not=j\operatorname{XOR} 1\) 就令 \(G_{ij}=1\)。接下来操作同上。答案为 $\sum\limits_{to_j=e}G_{1j} $。
2. 高斯消元
2.0 矩阵的初等变换
初等行变换:
- 交换两行
- 一行乘上非零标量
- 一行若干倍加到另一行
初等列变换也同理。
2.1 求解线性方程组
解方程:
显然,可以化作矩阵形式:\(A\vec x=\vec b\)。\(A\) 是系数矩阵。
其中 \([A|\vec b]\) 是增广矩阵。
对于增广矩阵做初等行变换,方程组解不变。
所谓高斯消元法 (Gauss Elimination) 就是通过初等行变换使矩阵化为行阶梯形。
而高斯-约旦消元法 (Gauss-Jordan Elimination) 就是在高斯消元后,反向消元,使矩阵化为行最简形。
2.1.1 解的判断
当消元出现行如:\(\begin{bmatrix}0&0&\cdots&|&1\end{bmatrix}\) 时,方程组无解。
除此以外,方程组必有解。
特别的,当消元后非零行构成严格三角形方程组,方程组必然有唯一解。
2.1.2 矩阵求逆
判断是否存在逆使 \(AA^{-1}=A^{-1}A=I\)。
类似的,不妨使增广增广矩阵为 \([A|I]\)。
我们通过高斯消元,使 \(A\) 变为行阶梯形,当其可逆时,当且仅当 \(A\) 有唯一解。因此此时 \(A\) 必然是严格的上三角矩阵。
接下来将 \(A\) 通过初等行变化为 \(I\),那么这个增广矩阵就变成了 \([I|A^{-1}]\)。
这是因为我们对 \(A\) 进行的变换相当于多个线性变换,根据 \(AI=A\),对于 \(A\) 变换的同时,\(I\) 同时变换成了 \(A^{-1}\)。
从这里我们可以得到一个充要结论:方阵可逆,解唯一。
2.1.3 随机游走
给定 \(n\) 点有向图,从 \(1\) 出发,每次等概率选一条出边。期望到 \(n\) 点的步数。
设 \(f_i\) 为从 \(i\) 出发期望多少步走到 \(n\) 点,有 \(f_i=\dfrac{\sum\limits_{i\to v\in E}f(v)}{deg_i}+1\)。
用高斯消元解这个方程组就好了。
3. 线性空间 & 线性组合 & 线性基
3.0 ...空间?
- 一维:数轴。向量表示:\(\vec a=\begin{bmatrix}a_1\end{bmatrix}\)
- 二维:平面直角坐标系。向量表示:\(\vec a=\begin{bmatrix}a_1\\a_2\end{bmatrix}\)
- 三维:空间直角坐标系。向量表示:\(\vec a=\begin{bmatrix}a_1\\a_2\\a_3\end{bmatrix}\)
呃呃,四维是啥?
可惜我们局限于三维世界,无法直观体验四维空间。但可以预见地发现,四维下的向量表示为 \(\vec a=\begin{bmatrix}a_1\\a_2\\a_3\\a_4\end{bmatrix}\)。
那么 \(n\) 维空间,也就对应着一个 \(n\) 维向量。容易发现空间内的点与向量是一一对应的,因此我们不妨暂时地把这俩东西当作相同。
3.1 线性空间(向量空间)
数学化的定义:一个向量集合 \(V\) 若 \(\forall \vec a,\vec b\in V\) 满足:
那么 \(V\) 就是一个线性空间。
3.2 线性组合
对于 \(n\) 个向量 \(\vec v_1,\vec v_2,\cdots,\vec v_n\),设 \(c_i\) 为常数,那么 \(\sum\limits_{i=1}^n c_i\vec v_i\) 就是它们的一个线性组合。
线性相关与线性无关
对于 \(n\) 个向量 \(\vec v_1,\vec v_2,\cdots,\vec v_n\),若存在一种系数不全为 \(0\) 的线性组合,使得 \(\sum\limits_{i=1}^n c_i\vec v_i=0\),那么称这些向量是线性相关的。
更容易理解的,如若通过数乘与相加运算可以将不包含第 \(i\) 个的向量通过另 \(n-1\) 个向量表示出来,那么这组向量就是线性相关的。
线性无关的定义类似,也就是不存在上述的线性组合。
3.3 张成空间(span)
\(n\) 个向量 \(\vec v_1,\vec v_2,\cdots,\vec v_n\in S\) 所构成的全体线性空间 \(V\) 即为这 \(n\) 个向量张成出的空间。
反之,任何一个线性空间都可以表示为 \(n\) 个向量的全体线性组合。
这 \(n\) 个向量称为线性空间的 生成元。
一组线性无关的生成元,称为一组 基。基的大小称作维数 \(\dim V\)。
显然易见,线性基是 线性无关 的。
3.4 基坐标
事实上,我们平常使用的平面直角坐标系是以两个垂直的单位向量作为基底,这两个向量张成出的空间便是这个平面,平面上点的坐标也便是我们所说的 基坐标。
下面给个定义。
对于一个线性空间 \(V\),若 \(\vec v_1,\vec v_2,\cdots,\vec v_n\) 是其一组基底,那么对于 \(V\) 中任意向量:
\((c_1,c_2,\cdots,c_n)\) 就是 \(\vec x\) 在 \(\vec v_1,\vec v_2,\cdots,\vec v_n\) 这组基下的坐标。
3.5 线性基
下面是 OI 中的线性基,通常解决异或类问题。
如果有 \(n\) 个向量 \(n\),不存在不全 \(0\) 的序列 \(a\) 满足:
那么称这 \(n\) 个向量「线性无关」。否则为「线性相关」。
对于异或线性基,即不存在异或和为 \(0\) 的非空子集。
3.5.1 构造线性基
从大到小枚举每一个二进制位,设第 \(a_v\) 为第 \(v\) 位的线性基。假设当前插入的值为 \(val\),\(a_v>0\) 时,\(val\gets a_v\operatorname{XOR}val\),继续枚举;反之,令 \(a_v\gets val\),退出。
3.5.2 例题
P4151 [WC2011] 最大XOR和路径
- Trick:异或两次相当于没有操作。
于是我们先对原图跑一次生成树,对于所有环,插入线性基。最后我们以 \(xorsum_{1\to n}\) 为 \(base\) 跑线性基 \(\max\) 即可。
P3292 [SCOI2016] 幸运数字
- 线性基类似 \(\max/\min\),可以合并。
Algorithm 1:考虑树剖,维护每一条重链的线性基,可以做到 4log。
Algorithm 2:考虑倍增,类似 LCA 的方式维护 \(u\) 第 \(1,2,4,8,\cdots\) 级祖先的线性基。可以做到 \(\mathcal O(n\log n\log^2 V+q\log^2 n\log V)\)。
Algorithm 3:考虑淀粉质,但我不会写。
前缀/后缀线性基
CF1100F Ivan and Burgers
3.6 秩
向量组的秩: \(\operatorname{rank}(S)=\dim\operatorname{span}(S)\)
矩阵的秩:\(\operatorname{rank}(A)\) 为矩阵列向量张成空间的维数,显然行秩与列秩相同。
对于 \(n\) 阶方阵 \(A\),当 \(\operatorname{rank}(A)=n\) 时,称其满秩,此时矩阵可逆。
*4. 线性变换
呃呃,什么是线性变换?
- 变换后原点位置不变
- 只会进行拉伸或者反转,不会使直线变成曲线。
这的确是一种极其简单的理解。
实际上,每一个线性变换都可以用 矩阵乘法 表示,如:\(f(\vec x)=A\times\vec x\),这就是一种线性变换。
而前面已经说过,矩阵乘法具有结合律,于是复合的线性变换即可如下计算:
线性变换与坐标系
实际上,线性变换还有更强的作用。
在空间中,我们实际上可以把线性变换矩阵 \(A\) 所有列向量认为是原来空间中的基经过线性变换后的新坐标。而将向量 \(\vec x\) 进行变换,也就是在新的基坐标下进行再次的对应。
这段几何意义上的变换,可以看看 3b1b 的视频,可以增进对线性组合这一行为的理解。
通过这个变换法则,我们还可以做出如下酷炫的行为!
- 逆时针旋转 \(\theta\):\(\begin{bmatrix}\cos\theta&-\sin\theta\\\sin\theta&\cos\theta\end{bmatrix}\)
希望大家能够从两种视角来深入理解线性代数中的这一部分,而不是简单了解它对于计算便利的作用。
5. 行列式
用于衡量高维空间的“有向体积”。
学习相关的一些定理后,其实会发现行列式是一种「广义容斥」。
5.1 定义
对于 \(n\) 阶方阵 \(A\),定义其行列式:
其中 \(S_n\) 为长度为 \(n\) 的全排列集合,\(\sigma\) 是其中一个排列。当排列的逆序对数为奇时,\(\operatorname{sgn(\sigma)}=-1\)。反之, \(\operatorname{sgn(\sigma)}=1\)。
类似的,积和式的定义为:
\(\operatorname{perm}(A)=\sum\limits_{\sigma\in S_n}\prod\limits_{i=1}^na_{i,\sigma(i)}\)
在 \(\bmod 2\) 意义下,\(\operatorname{perm}(A)=\det(A)\),这是由于 \(-1\equiv1\pmod 2\)。
5.2 性质
5.2.1 初等代数变换对 \(\det\) 的影响
- 矩阵转置,行列式不变。
- 行 (列) 交换,行列式取反。
- 行 (列) 的 \(k\) 倍加到另一行 (列) 上,行列式不变。
- 行 (列) 数乘 \(k\),行列式等比例变大。
5.2.2 简单性质
-
单位化:\(\lvert I\rvert=1\)。
-
线性性:\(\begin{vmatrix}a_{11} & \cdots & pa_{1i}+qb_{1i}&\cdots&a_{1n}\\\vdots&\vdots&\vdots&\vdots&\vdots\\a_{n1} & \cdots & pa_{ni}+qb_{ni}&\cdots&a_{nn}\end{vmatrix}=p\begin{vmatrix}a_{11} & \cdots & a_{1i}&\cdots&a_{1n}\\\vdots&\vdots&\vdots&\vdots&\vdots\\a_{n1} & \cdots & a_{ni}&\cdots&a_{nn}\end{vmatrix}+q\begin{vmatrix}a_{11} & \cdots & b_{1i}&\cdots&a_{1n}\\\vdots&\vdots&\vdots&\vdots&\vdots\\a_{n1} & \cdots & b_{ni}&\cdots&a_{nn}\end{vmatrix}\)
对于行也适用。
-
\(\det (AB)=\det(A)\det(B)\)
-
满秩时行列式不为 \(0\)。
-
对于上三角矩阵的行列式,其值等于主对角线乘积。
5.2.3 求法
通过初等代数边换,得到上三角矩阵。
最后 \(\prod a_{ii}\) 即为行列式值。
取模时,若模数非质数不存在逆元,需要对两行 (列) 辗转相减消元。
模板题。
5.3 Matrix-Tree 定理
Matrix-Tree 通常用于解决生成树计数问题。
5.3.1 无向图的矩阵树定理
设 \(D\) 为无向图的度数矩阵(即 \(D_{ii}=deg_i\),其余情况为 \(0\)),\(E\) 为无向图的邻接矩阵。记 \(L=D-E\),\(L\) 就是「Laplace 矩阵 (Laplacian Matrix)」。设 \(K'\) 为 \(K\) 的 \(n-1\) 阶主子式,也就是 \(K\) 同时去掉任意第 \(i\) 行第 \(i\) 列后的矩阵(因为在连通图中,去掉任意一行一列后行列式值不变)。
那么该无向图的生成树个数就是 \(\deg K'\)。
5.3.2 有向图的矩阵树定理
既然是有向图,那么自然会分成内向树和外向树。设根为 \(R\)。
设 \(D_{in}\) 为有向图入度矩阵,\(D_{out}\) 为有向图的出度矩阵。
\(L_{in}=D_{in}-G\),\(L_{out}=D_{out}-G\)。\(L'_{in},L'_{out}\) 为去掉 \(R\) 行 \(R\) 列后的矩阵,那么外向生成树的个数为 \(\det L'_{in}\),内向生成树的个数为 \(\det L'_{out}\)。
5.3.3 生成树的边权积之和
一条边 \((u,v,w)\) 可以看作是有 \(w\) 条重边 \((u,v)\),因此可转化为普通的生成树计数。
可以拓展到实数,以下是实数边权的 Cpp 代码。
double det () {
double sgn = 1;
for (int i = 1; i < n; i ++) {
if (!L[i][i])
for (int j = i + 1; j < n; j ++)
if (L[j][i]) {
swap (L[j], L[i]);
sgn = -sgn;
break ;
}
for (int j = i + 1; j < n; j ++) {
double div = L[j][i] / L[i][i];
for (int k = i; k < n; k ++)
L[j][k] = L[j][k] - div * L[i][k];
}
}
double ret = 1;
for (int i = 1; i < n; i ++)
ret = ret * L[i][i];
return ret * sgn;
}
5.3.4 生成树的边权和之和
我们只需使维护的值变为多项式 \(1+wx\) 即可。最终答案就是求 \(\bmod x^2\) 下的最高次系数。
考虑如何高斯消元。对于两个多项式 \(A+Bx\) 与 \(C+Dx\),加减乘操作是简单的。
对于除法操作,\(\dfrac {A+Bx}{C+Dx}\equiv\dfrac{(A+Bx)(C-Dx)}{C^2-(Dx)^2}\equiv\dfrac AC+\dfrac{BC-AD}{C^2}x\) 。
modll det () {
int sgn = 1;
for (int i = 1; i < n; i ++) {
if (!L[i][i].a.x)
for (int j = i + 1; j < n; j ++) {
if (L[j][i].a.x) {
sgn = -sgn;
swap (L[j], L[i]);
break ;
}
}
for (int j = i + 1; j < n; j ++) {
poly div = L[j][i] / L[i][i];
for (int k = i; k < n; k ++)
L[j][k] = L[j][k] - div * L[i][k];
}
}
poly res = {1, 0};
for (int i = 1; i < n; i ++) res = res * L[i][i];
return res.b * sgn;
}
这里的 modll 是一个剩余系类。
5.3.5 例题
P4336 [SHOI2016] 黑暗前的幻想乡
我们先求全集的生成树个数,接下来考虑容斥。
这是一个十分经典的子集反演形式。设 \(f_S\) 表示至多选集合 \(S\) 内边集的方案数,\(g_S\) 为恰选 \(S\) 集合内所有边集至少一个的方案数。那么有:
\(f_S=\sum\limits_{T\subseteq S}g_T\iff g_S=\sum\limits_{T\subseteq S}(-1)^{\lvert S\rvert+\lvert T\rvert}f_T\)
注意到 \(f_S\) 是好求的,我们只要在做 Matrix-Tree 时把 \(S\) 包含的边放到 Laplace 矩阵里即可。
之后容斥即可。时间复杂度 \(\mathcal O(2^nn^3)\)。
int U = 1 << (n - 1); modll ans = 0;
for (int i = 0; i < U; i ++) {
ll pocount = 0;
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= n; j ++)
L[i][j] = 0;
for (int j = 0; j < n - 1; j ++)
if (i >> j & 1) {
++ pocount;
for (auto [u, v] : e[j])
L[u][u] = L[u][u] + 1, L[v][v] = L[v][v] + 1, L[u][v] = L[u][v] - 1, L[v][u] = L[v][u] - 1;
}
pocount = n - pocount - 1;
if (pocount & 1) pocount = p - 1;
else pocount = 1;
ans = ans + det () * pocount;
}
P6624 [省选联考 2020 A 卷] 作业题
枚举 \(\gcd\),考虑容斥。由 \(\varphi*1=\operatorname {id}\),所求即为 \(\sum\limits_{e\in T} w_e\times\sum\limits_{d\mid w_1,d\mid w_2,\cdots}\varphi(d)\)。
枚举 \(d\),那么即为 \(\sum\limits_{d}\varphi(d)\sum\limits_{e\in T'}w_e\),这里的 \(T'\) 中树边权均被 \(d\) 整除。
考虑 5.3.4 中的计数方法,发现这个复杂度是 \(\mathcal O(Vn^3)\) 的,显然过不了。
当 \(d\) 的倍数出现了至少 \(n-1\) 次才需要求解,且总因子个数为 \(\sum\sigma_0(w_i)\),那么至多有 \(\dfrac{\sum\sigma_0(w_i)}{n-1}\) 个 \(d\) 合法。这是一个很松的上界。我们对这些合法的 \(d\) 跑 Matrix-Tree 即可。
modll ans = 0ll;
for (int i = 1; i <= mx; i ++) {
ll phi = getPhi (i);
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= n; j ++)
L[i][j] = {0, 0};
int tmp = 0;
for (auto [u, v, w] : edge) {
if (w % i) continue;
++ tmp;
L[u][u] = L[u][u] + (poly){1, w};
L[v][v] = L[v][v] + (poly){1, w};
L[v][u] = L[v][u] - (poly){1, w};
L[u][v] = L[u][v] - (poly){1, w};
}
if (tmp < n - 1) continue;
ans = ans + det () * phi;
}
P3317 [SDOI2014] 重建
就是求 \(\sum\limits_{T}\prod\limits_{e\in T}p_e\prod\limits_{e'\not\in T}(1-p_{e'})\)。
我们尝试将没有特殊性质的所有边放在外边,有特殊性质的放在里面。
\(\begin{aligned}&=\sum\limits_{T}\prod\limits_{e\in T}p_e\dfrac{\prod\limits_{u}(1-p_{u})}{\prod\limits_{e'\in T}(1-p_{e'})}\\&=\prod\limits_{u}(1-p_u)\sum\limits_T\prod_{e\in T}\dfrac{p_e}{1-p_e}\end{aligned}\)
前面是个无关系数,后面是 Matrix-Tree 的标准形式。
scanf ("%d", &n); double ans = 1;
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= n; j ++) {
scanf ("%lf", &P[i][j]);
if (fabs (P[i][j]) < eps) P[i][j] = eps;
else if (fabs (1 - P[i][j]) < eps) P[i][j] = 1.0 - eps;
if (j > i) ans *= (1.0 - P[i][j]);
if (i != j) L[i][j] = P[i][j] / (1 - P[i][j]);
if (i != j) L[i][i] += L[i][j], L[i][j] = -L[i][j];
}
printf ("%.6f\n", ans * det ());
5.4 BEST 定理
用于解决欧拉图的欧拉回路计数问题。
5.4.1 定理内容
设 \(G\) 为有向欧拉图,那么 \(G\) 的不同欧拉回路总数
\(t^{\operatorname{root}}(G,k)\) 表示 \(G\) 中以 \(k\) 为根的内向树个数,可以用 Matrix-Tree 定理计算。
由欧拉图的优秀性质,对于任意 \(i,j\) 都有 \(t^{\operatorname{root}}(G,i)=t^{\operatorname{root}}(G,j)\),且各点度数相同。
5.4.2 例题
还没遇到相关的题目……
5.5 LGV 引理
用于解决 DAG 上不相交路径计数问题。
5.5.1 引理内容
对于一个 DAG,设起点集合 \(A=\{a_1,\cdots,a_n\}\),终点集合 \(B=\{b_1,\cdots b_n\}\)。对于一个有向路径 \(P\),定义 \(\omega (P)\) 为 \(P\) 上边权之积。对于任意顶点 \(u,v\),定义 \(e(u,v)=\sum\limits_{P:u\to v}\omega(P)\),也就是 \(u\) 到 \(v\) 的每一条路径的 \(\omega (P)\) 之和。
设一组 \(A\) 到 \(B\) 的路径集合 \(S\),且 \(\forall i,j\),\(S_i\) 与 \(S_j\) 无公共点,且每一条路径恰好连接了 \(A\) 中的一个数与 \(B\) 中一个数,每个顶点恰有一条路径连接。那么 \(S\) 的长度必然为 \(n\),因此构成一个排列 \(\sigma(S)\),\(S_i\) 的起点为 \(a_i\),终点为 \(b_i\)。
记 \(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}\),则有:
也就是说矩阵行列式的值,等于所有 \(\omega(S)\) 的带符号和。
这是因为对于存在逆序对的方案而言,奇逆序对数的方案与另一个偶逆序对数的方案是一一对应的。
对于满足这一性质:
- 当 \(\sigma (S)\) 存在逆序对时,路径有交。
那么对于所有的 \(i\) 求出 \(a_i\to b_i\) 的无交路径计数,就可以使用 LGV 引理。
5.5.2 例题
P6657 【模板】LGV 引理
题设满足了这一性质。
\(e(a_i,b_j)\) 直接用组合数,为 \(\dbinom{b_j-a_i+n-1}{n-1}\)。
答案就是 \(\det M\)。因为我们所求的是所有 \(a_i\to b_i\) 的不交路径计数,显然是没有逆序对的。
P7736 [NOI2021] 路径交点
设第 \(i\) 层到第 \(i+1\) 层的奇数交点方案为 \(f_i\),偶数交点方案为 \(g_i\)。那么 \((f_if_{i+1}+g_ig_{i+1})-(f_ig_{i+1}+g_if_{i+1})=(f_i-g_i)(f_{i+1}-g_{i+1})\) 为第 \(i\) 层到第 \(i+2\) 层的答案。
以此类推,第 \(1\) 层到第 \(k\) 层的答案就是 \(\prod\limits_{i=1}^{k-1}(f_i-g_i)\)。
因此,我们只需求出每一层的 \(f_i-g_i\) 即可。由于交点奇偶性等价于逆序对数奇偶性,因此这实际上就是其邻接矩阵行列式之值,根据 \(\det(AB)=\det A\det B\),相当于把邻接矩阵乘积后的行列式值。
while (T --) {
scanf ("%d%d", &k, &n); int t = n; int sb; n <<= 1;
rep (i, 2, k) scanf ("%d", nn + i), n = max (n, nn[i]);
rep (i, 2, k) scanf ("%d", m + i); ans.identify (); n = t;
rep (i, 2, k) {
int u, v;
Matrix tmp; n = max (nn[i], (i > 2 ? nn[i - 1] : n));
rep (j, 1, m[i]) scanf ("%d%d", &u, &v), tmp[u][v] = 1;
ans = ans * tmp;
} n = t;
printf ("%lld\n", det ());
}
ABC216H Random Robots
把时间轴看成 y 轴,这就化作了一个网格图。那么每一秒相当于选择:\((x,y)\to(x,y+1)\) 或者 \((x,y)\to(x+1,y+1)\)。同时发现这个概率是假的,实际上求出方案数后乘上 \(\dfrac 1{2^{nk}}\) 即可。
假设我们确定了单调递增的终点集合 \(B\),根据 LGV 引理,那么答案就是 \(\sum\limits_{P}\operatorname{sgn}(P)\prod{A_{i,P_i}}\)。
这里的 \(A_{i,j}\) 表示从 \((0,x_i)\) 走到 \((n,b_j)\) 的方案数,显然为 \(\dbinom{n}{b_j-x_i}\)。
考虑 dp,设 \(f(S,i)\) 表示 \(S\) 内的机器人终点在不超过横坐标 \(i\) 的方案数。根据写出的答案式,有转移:
\(f(S,i)=f(S,i-1)+\sum\limits_{j\in S}f(S-\{j\},i-1)\times\dbinom{n}{i-x_j}\times\tau(S,i)\)
这里的 \(\tau(S,i)\):当集合内大于 \(i\) 的个数为奇数时,\(\tau (S,i)=-1\);当集合内大于 \(i\) 的个数为偶数时,\(\tau (S,i)=1\)。
答案即为 \(f(U,n+x_k)\times 2^{nk}\)。\(U\) 是全集。
scanf ("%d%d", &k, &n);
for (int i = 1; i <= k; i ++) scanf ("%d", x + i), x[i] ++;
f[0][0] = 1; int U = (1 << k) - 1;
for (int i = 1; i <= x[k] + n; i ++) {
for (int S = 0; S <= U; S ++) {
f[S][i] = f[S][i - 1]; bool flag = true;
for (int j = 0; j < k; j ++)
if (S >> j & 1) {
if (x[j] > i) { flag = false; break; }
modll coes = (__builtin_popcount ((S >> j) ^ 1) & 1) ? (p - 1) : 1;
f[S][i] = f[S][i] + f[S ^ (1 << j)][i - 1] * comb (n, i - x[j + 1]) * coes;
}
if (!flag) {
f[S][i] = 0;
continue;
}
}
}
printf ("%lld\n", f[U][ n + x[k] ] / ((modll)(2) ^ (n * k)));

浙公网安备 33010602011771号