线代相关

高斯消元

用于解方程组的有力工具,也是通过 线性变换 简化矩阵的好东西。使用 \(\rm Gauss-Jordan\) 消元法可以把矩阵消成对角矩阵,是计算行列式的基础。算法流程如下:

  • 对于变量 \(x_1, x_2 \dots x_n\),考虑令 \(x_i\) 为第 \(i\) 行的主元,即最后把 \(x_i\) 前的系数消成除第 \(i\) 行外都为 \(0\)。假设现在考虑到主元 \(x_i\)

  • 那么先找到某一行 \(p\),满足 \(a_{p, i} \neq 0\),不妨交换 \(p\)\(i\) 两行。

  • 然后拿第 \(i\) 行的 \(x_i\) 去消掉每一行 \(x_i\) 前的系数。

时间复杂度 \(O(n^3)\)。值得一提的是,若最后某一个方程 \(a_ix_i = q_i\)\(a_i = q_i = 0\),则 \(x_i\) 为自由元,若 \(q_i \neq 0\),则方程无解。

矩阵求逆

定义一个矩阵 \(\mathrm G\)逆矩阵唯一 的一个矩阵 \(\mathrm P\)\(G \times P = \mathrm I\)。那么由于矩阵相当于一个线性变换,也就可以看作把 \(G\) 变成 \(\mathrm I\) 的变换过程。于是考虑模拟这个过程。

那么从 \(G\) 变成 \(\mathrm I\) 显然需要先把 \(G\) 变成对角矩阵,于是考虑高斯消元。每次对一行减掉一些数,就可以在 \(P\) 矩阵中做一样的事情。最后 \(P\) 的第 \(i\) 行都乘上 \(a_i\) 的逆元即可。

时间复杂度 \(O(n^3)\)

行列式

定义矩阵 \(A\) 的行列式为如下柿子:\(\sum_{p}(-1)^{\tau(p)}\prod_{i=1}^nA_{i,p_i}\),其中 \(\tau(p)\) 为排列 \(p\) 的逆序对个数。写作 \(\det(A)\) 或者 \(|A|\)。想要直接快速求值实际上是困难的,我们需要一些性质。

  1. 交换矩阵 \(A\) 的两行,\(|A|\) 的符号改变。

    这个实际上是通过改变了 \(\tau(p)\) 的奇偶性,从而交换了奇排列和偶排列的贡献。

  2. 矩阵 \(A\) 若有两行 \(i, j\) 完全相等,则 \(|A| = 0\)

    证明:若交换 \(A_i, A_j\) 两行,得到矩阵 \(A'\)。由定理一可得 \(|A'| = -|A|\),但是矩阵 \(A'\) 显然与矩阵 \(A\) 完全相等,所以 \(|A'| = |A|\),从而 \(|A| = 0\)

  3. 将矩阵 \(A\) 一行乘上常数 \(c\),则 \(|A| \gets |A| \times c\)

    由乘法分配律即可。

  4. 将矩阵 \(A\) 的某一行 \(A_x\) 整体加上另一行 \(A_y\) 得到 \(|A'|\)\(|A| = |A'|\)

    首先 \(|A'| = \sum_p{(-1)^{\tau(p)} \prod _{i \neq x} A_{i, p_i} \times (A_{x, p_x} + A_{y, p_x})} = \sum_p{(-1)^{\tau(p)} \prod _{i = 1} ^n A_{i, p_i}} + \sum_p{(-1)^{\tau(p)} \prod _{i \neq x} A_{i, p_i} \times A_{y, p_x}}\)
    那么令 \(B\) 等于 \(A\) 把第 \(x\) 行改为第 \(y\) 行得到的矩阵。则 \(|A'| = |A| + |B|\)。然后注意到 \(B\) 矩阵中第 \(x\) 行与第 \(y\) 行相等,于是由定理 \(2\) 则有 \(|B| = 0\)。于是 \(|A'| = |A|\)

    • 于是由该定理和定理 \(3\) 可以得到一个推论,把矩阵的某一行加上另一行的 \(k\) 倍,矩阵行列式不变。

有了这些定理,我们就可以来考虑如何求矩阵的行列式了。注意到若对矩阵进行高斯消元,则行列式只有可能变号,那可以把矩阵消成对角矩阵,记录交换次数的奇偶性,然后把对角线乘起来就好了。

时间复杂度 \(O(n^3)\)

当然模板题中 \(p\) 不一定是质数,所以不一定有逆元,那么可以把两行辗转相除,由于每次那一行一定至少 \(/ 2\),因此最多操作 \(O(n^2 \log p)\) 次就可以操作完所有的 \(A_{i, j}\)。于是时间复杂度 \(O(n^3 + n^2 \log p)\)

qwq
  for(int i = 1; i <= n; i++){
    for(int j = i + 1; j <= n; j++){
      while(a[i][i]){
        ll q = a[j][i] / a[i][i];
        for(int k = i; k <= n; k++) ADD(a[j][k], mod - q * a[i][k] % mod);
        swap(a[i], a[j]); fl ^= 1;
      } swap(a[i], a[j]); fl ^= 1;
    }
  }
  for(int i = 1; i <= n; i++) MUL(ans, a[i][i]);
  if(fl) MUL(ans, mod - 1);

当然行列式还有更多的性质:

  1. \(|AB| = |A| \times |B|\)

    通过上面的消元方法,可以把矩阵 \(A, B\) 分别消成对角矩阵 \(A', B'\),若在 \(A \to A'\) 的过程中交换了奇数次,则令 \(A'_{1, 1} \gets -A'_{1, 1}\) 即可,\(B'\) 也是类似。则此时 \(\det A = \det A', \det B = \det B'\),然后此时 \(\det(A' \times B') = \det A' \times \det B'\),再令 \(X \times A = A', Y \times B = B'\),然后这两个东西都对行列式没有贡献,从而 \(\det (AB) = \det (A'B')\)。于是 \(\det(AB) = \det A \times \det B\)

  2. for more.

Matrix-tree 定理

对于一张有向图 \(G\),定义其入度 \(\rm Laplace\) 矩阵 \(L_{in}\) 为:

  • \(i = j:\) \(L_{in_{i, i}} = \deg_{in_i}\)

  • \(i \neq j:\) \(L_{in_{i, j}} = -G_{i, j}\)

其中 \(G_{i, j}\)\(G\) 的邻接矩阵。

\(\rm Matrix-tree\) 定理:\(L_{in_{r, r}}\) 的余子式等于以 \(r\) 为根的 叶向生成树 个数。(下面记作 \(T^{leaf}(r)\)

(余子式:对于矩阵 \(A\)\(A_{i, j}\) 的余子式即为 \(\det A_{[n] \setminus \{i\}, [n]\setminus \{j\}}\) 的行列式,其中 \([n] = \{1, 2, \dots n\}\)

  • 证明:不妨令 \(r = 1\)。首先一个 naive 的想法,除了 \(1\) 以外的每个点都需要一条入边,方案数为 \(\prod_{i = 2}^n \deg_i\)。但是这样的东西可能会产生环,需要容斥算一下。

    考虑定义排列 \(p = \{p_1, p_2, \dots p_n\}\) 来描述钦定某一些环的贡献:

    • \(p_i = i\),则表示钦定 \(i\) 不在环上,则贡献为 \(\deg_i\)

    • \(p_i \neq i\),则表示 \(p_i\)\(i\) 在环上的下一个点,则贡献是 \(i \to p_i\) 的边数,即 \(G_{i, j}\)

    \(f(p)\)\(p\) 上环的个数,\(g(p)\) 为环上点的个数。不难发现此时的方案数即为 \(\sum_p {(-1)^{f(p) - g(p)} \prod _{i = 2} ^n L_{in_{i, j}}}\),减了个 \(g(p)\) 是因为对于每个在环上的点,还得扔个负号进 \(\rm Laplace\) 矩阵里面。

    但是根据这样的描述可以把容斥系数 \(p\) 拆给若干个置换,注意到 \(\tau(p)\) 的奇偶性与 \(p' = \{1, 2, \dots n\}\) 通过交换得到 \(p\) 所需要的次数的奇偶性相同。其中每个置换 \(S\) 显然需要交换 \(|S| - 1\) 次,求一下和就是 \(f(p) - g(p)\),也就是 \(\tau(p) \equiv f(p) - g(p)\pmod 2\)。于是这个柿子也变成了 \(\sum_p(-1)^{\tau(p)} \prod _{i = 2}^n {L_{in_{i, j}}} = {L_{in_{i, j}}}_{[n] \setminus \{i\}, [n]\setminus \{j\}}\),即 \(L_{in_{1, 1}}\) 的余子式。

类似的,我们还可以定义出度 \(\rm Laplace\) 矩阵 \(L_{out}\),此时有 \(L_{out_{r, r}}\) 的余子式等于以 \(r\) 为根的 根向生成树。也可以拓展到无向图,此时就是 生成树 个数。甚至还可以拓展到有边权的情况,只要把邻接矩阵改为边权组成的矩阵即可求出 \(\sum_T \prod_{e \in T} w_e\) 的值,然后把度数改成连边的边权和即可。若有重边,根据加法原理,把多条边的边权加起来即可。

那么对于一个图的生成树,通过求行列式的方式,就可以做到 \(O(n^3)\) 了。

拓展一下,若对于一棵树的权值设为其所有边权的乘积,那么可以将 \(L_{i, j}\) 改成 \(-w_{i, j}\)\(L_{i, i} = \sum_{e = (i, v)} w_e\)。再做矩阵树定理即可。

继续拓展一下,若对于一个树的权值设为其所有边权的和,那么我们可以构造一个 集合幂级数 来刻画加法,即构造 \((1 + x^w)\)。但是这样的时间复杂度是 \(O(n^3V)\) 的。注意到加法具有很好的拆贡献的性质,所以我们可以考虑对于一条边强制钦定选择,并求出对应的生成树方案数,那么我们相当于对于一种选择方案和一条钦定边,令钦定边的权值为原来的权值,其他边的权值均为 \(1\),然后乘起来。具体的,我们还是构造一个集合幂级数来刻画选择的边的条数,即 \((wx + 1)\)。此时 \(1\) 次项即为答案,所以每次乘法时可以删去二次项。时间复杂度即为 \(O(n^3)\)

还可以拓展:)若此时一个选择方案的权值为卷积怎么办。那么我们构造集合幂级数作为边权,即 \((1 + x^w)\),一个选择方案的权值的相当于通过卷积得到的集合幂级数。时间复杂度 \(O(n^3 \times T(V))\)\(T(V)\) 为对大小为 \(V\) 的卷积的时间复杂度。

但是有一些特殊情况。比如权值为位运算卷积,直接 fwt 的时间复杂度有 \(O(n^3 V \log V)\),但是我们可以对于每个边权,提前处理出 fwt 后的结果,并且对于每一位分别做一次矩阵树定理,最后再 ifwt 回去。此时的时间复杂度为 \(O(n^3 V + n^2 V \log V)\) 的。对于其他线性变换也是如此,基本可以优化掉一个老哥。

qwq
ll det(){
  ll ans = 1;
  for(int i = 1; i <= n; i++){
    for(int j = i + 1; j <= n; j++) if(a[j][i]){swap(a[j], a[i]), ans = mod - ans; break;}
    if(!a[i][i]) return 0;
    ll c = qpow(a[i][i], mod - 2);
    for(int j = i + 1; j <= n; j++){
      ll q = a[j][i] * c % mod;
      for(int k = i; k <= n; k++) ADD(a[j][k], mod - q * a[i][k] % mod);
    }
  }
  for(int i = 1; i <= n; i++) MUL(ans, a[i][i]);
  return ans;
}

signed main(){
  ios::sync_with_stdio(0);
  cin.tie(0); cout.tie(0);
  cin >> n >> m >> t;
  for(int i = 1; i <= m; i++){
    int x, y, w; cin >> x >> y >> w;
    if(t == 0){
      ADD(L[x][y], mod - w); ADD(L[y][x], mod - w);
      ADD(L[x][x], w); ADD(L[y][y], w);
    } else{
      ADD(L[x][y], mod - w); ADD(L[y][y], w);
    }
  }
  for(int i = 1; i < n; i++)
    for(int j = 1; j < n; j++)
      a[i][j] = L[i + 1][j + 1];
  n--;
  cout << det();

  return 0;
}

BEST 定理

\(\rm BEST\) 定理是将欧拉回路计数转化成生成树计数的有力工具,它叙述了这么个东西:

对于一张有向欧拉图 \(G\),其欧拉回路的个数为(循环同构算一个):

\[T^{root}(G, k) \prod_{i = 1}^n (\deg_i - 1) \]

其中 \(k\) 可以任意选择。这个定理的证明还是很 \(\rm Educational\) 的,有必要学习。

  • 证明/推导:

    首先考虑对这个东西赋予一个组合意义,令根为回路的起点,那么注意到每个点的最后一条出边一定可以组成一个根向生成树。于是令树边都是每个点的最后一条边,然后对每条非树边钦定一个顺序,其中方案数为 \(\prod (\deg_i - 1)\)。那么现在就是证明这两个东西构成双射。首先每条欧拉回路显然都可以唯一对应到这个东西,于是只需要证明每个生成树都是合法的即可,即证明不会有任何一条边不被经过。那么注意到若一条树边没有被经过,则会对其父节点产生一个入度,则一路推下去,会发现根节点还有至少 \(1\) 的入度,由于欧拉图的特性,则根节点至少还有一条边没有被经过,与最后一条出边显然矛盾。于是构成双射。

posted @ 2025-05-23 22:06  Little_corn  阅读(22)  评论(0)    收藏  举报