特征多项式

定义

我们考虑一个 的矩阵 ,其中 。它的特征多项式记为 其中

其中 为一个 的单位矩阵。一些地方会定义为 与我们的定义仅相差了一个符号 ,但我们采用这种定义得到的 一定为首一多项式,而另外的定义则仅当 为偶数时才是首一多项式。需要注意的是 的矩阵行列式为 是良定义的。

过程

的矩阵 为上三角矩阵如

那么

可轻松求得,下三角矩阵也是类似的。但如果 不属于这两种矩阵,我们需要使用相似变换来使得矩阵变为容易求得特征多项式的形式。

相似变换

对于 的矩阵 ,当存在 的可逆矩阵 满足

我们说 相似,记变换 为相似变换。且我们说 有相同的特征多项式。

考虑

得证,且我们发现 也是一样的。另外 ,因为

使用高斯消元进行相似变换

的矩阵 进行高斯消元行变换的三个基本操作有

  • 的第 行互换:
  • 的第 行乘
  • 的第 行的 倍加到第 行:

对于 这几个 的初等矩阵分别为

其中两个 分别在 的第 行,注意

其中 的第 行第 列,注意

其中 的第 行第 列,注意

若我们记 为第 行第 列的元素为 、其余为零的 矩阵,那么

易验证其逆矩阵。

我们在对矩阵使用上述操作(左乘初等矩阵)后再右乘其逆矩阵即相似变换,左乘为行变换,易发现右乘即列变换。

若我们能将矩阵通过相似变换变为上三角或下三角的形式,那么可以轻松求出其特征多项式。但我们发现若对主对角线上的元素应用变换 后会导致原本通过 将第 行第 列的元素消为零后右乘 即将 的第 列的 倍加到第 列这一操作使得之前消为零的元素现在可能不为零,可能不能将其变为上三角或下三角形式。

后文将说明我们对次对角线上的元素应用变换后得到的矩阵依然可以轻松得到其特征多项式。

上 Hessenberg 矩阵

对于 的形如

的矩阵我们称为上 Hessenberg 矩阵,其中 为次对角线。

我们使用相似变换将次对角线以下的元素消为零后即能得到上 Hessenberg 矩阵,而求出一个 上 Hessenberg 矩阵的特征多项式则可在 时间完成。

我们记 为只保留 的前 行和前 列的矩阵,记 那么

在计算行列式时我们一般选择按零最多的行或列余子式展开,余子式即删除了当前选择的元素所在行和列之后的矩阵,在这里我们选择按最后一行进行展开,有

观察并归纳,对

至此完成了整个算法,该算法一般被称为 Hessenberg 算法。

应用

在信息学中我们一般考虑 上的矩阵,通常 为素数,进行上述相似变换是简单的,当 为合数时,我们可以考虑类似辗转相除的方法来进行。

实现
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#include <cassert>
#include <iostream>
#include <random>
#include <vector>

typedef std::vector<std::vector<int>> Matrix;
typedef long long i64;

Matrix to_upper_Hessenberg(const Matrix &M, int mod) {
  Matrix H(M);
  int n = H.size();
  for (int i = 0; i < n; ++i) {
    for (int j = 0; j < n; ++j) {
      if ((H[i][j] %= mod) < 0) H[i][j] += mod;
    }
  }
  for (int i = 0; i < n - 1; ++i) {
    int pivot = i + 1;
    for (; pivot < n; ++pivot) {
      if (H[pivot][i] != 0) break;
    }
    if (pivot == n) continue;
    if (pivot != i + 1) {
      for (int j = i; j < n; ++j) std::swap(H[i + 1][j], H[pivot][j]);
      for (int j = 0; j < n; ++j) std::swap(H[j][i + 1], H[j][pivot]);
    }
    for (int j = i + 2; j < n; ++j) {
      for (;;) {
        if (H[j][i] == 0) break;
        if (H[i + 1][i] == 0) {
          for (int k = i; k < n; ++k) std::swap(H[i + 1][k], H[j][k]);
          for (int k = 0; k < n; ++k) std::swap(H[k][i + 1], H[k][j]);
          break;
        }
        if (H[j][i] >= H[i + 1][i]) {
          int q = H[j][i] / H[i + 1][i], mq = mod - q;
          for (int k = i; k < n; ++k)
            H[j][k] = (H[j][k] + i64(mq) * H[i + 1][k]) % mod;
          for (int k = 0; k < n; ++k)
            H[k][i + 1] = (H[k][i + 1] + i64(q) * H[k][j]) % mod;
        } else {
          int q = H[i + 1][i] / H[j][i], mq = mod - q;
          for (int k = i; k < n; ++k)
            H[i + 1][k] = (H[i + 1][k] + i64(mq) * H[j][k]) % mod;
          for (int k = 0; k < n; ++k)
            H[k][j] = (H[k][j] + i64(q) * H[k][i + 1]) % mod;
        }
      }
    }
  }
  return H;
}

std::vector<int> get_charpoly(const Matrix &M, int mod) {
  Matrix H(to_upper_Hessenberg(M, mod));
  int n = H.size();
  std::vector<std::vector<int>> p(n + 1);
  p[0] = {1 % mod};
  for (int i = 1; i <= n; ++i) {
    const std::vector<int> &pi_1 = p[i - 1];
    std::vector<int> &pi = p[i];
    pi.resize(i + 1, 0);
    int v = mod - H[i - 1][i - 1];
    if (v == mod) v -= mod;
    for (int j = 0; j < i; ++j) {
      pi[j] = (pi[j] + i64(v) * pi_1[j]) % mod;
      if ((pi[j + 1] += pi_1[j]) >= mod) pi[j + 1] -= mod;
    }
    int t = 1;
    for (int j = 1; j < i; ++j) {
      t = i64(t) * H[i - j][i - j - 1] % mod;
      int prod = i64(t) * H[i - j - 1][i - 1] % mod;
      if (prod == 0) continue;
      prod = mod - prod;
      for (int k = 0; k <= i - j - 1; ++k)
        pi[k] = (pi[k] + i64(prod) * p[i - j - 1][k]) % mod;
    }
  }
  return p[n];
}

bool verify(const Matrix &M, const std::vector<int> &charpoly, int mod) {
  if (mod == 1) return true;
  int n = M.size();
  std::vector<int> randvec(n), sum(n, 0);
  std::mt19937 gen(std::random_device{}());
  std::uniform_int_distribution<int> dis(1, mod - 1);
  for (int i = 0; i < n; ++i) randvec[i] = dis(gen);
  for (int i = 0; i <= n; ++i) {
    int v = charpoly[i];
    for (int j = 0; j < n; ++j) sum[j] = (sum[j] + i64(v) * randvec[j]) % mod;
    std::vector<int> prod(n, 0);
    for (int j = 0; j < n; ++j) {
      for (int k = 0; k < n; ++k) {
        prod[j] = (prod[j] + i64(M[j][k]) * randvec[k]) % mod;
      }
    }
    randvec.swap(prod);
  }
  for (int i = 0; i < n; ++i)
    if (sum[i] != 0) return false;
  return true;
}

int main() {
  std::ios::sync_with_stdio(false);
  std::cin.tie(0);
  int n, mod;
  std::cin >> n >> mod;
  Matrix M(n, std::vector<int>(n));
  for (int i = 0; i < n; ++i)
    for (int j = 0; j < n; ++j) std::cin >> M[i][j];
  std::vector<int> charpoly(get_charpoly(M, mod));
  for (int i = 0; i <= n; ++i) std::cout << charpoly[i] << ' ';
  assert(verify(M, charpoly, mod));
  return 0;
}

上述 Hessenberg 算法不具有数值的稳定性,所以 上的矩阵在使用前需要其他算法进行调整或改用其他具有数值稳定性的算法。

我们可以将特征多项式与常系数齐次线性递推联系起来,也可结合 Cayley-Hamilton 定理、多项式取模加速一些域上求矩阵幂次的算法。

Cayley-Hamilton 定理指出

其中 的零矩阵, 的特征多项式。

若我们要求 其中 较大,那么可以求出 后利用

显然。我们令 那么

可以发现计算 大约需要 次矩阵与矩阵的乘法。

参考文献


评论