上上次说了一个模n矩阵的快速计算的两种方法,其实如果能把矩阵“对角化”的话,还可以进一步减少运算复杂度(不过这种方法在模n/模p下比较看运气);另外,上次在说GL(n,p)的群阶时说了两个简略的证明,如果用Jordan Canonical Form来解释的话好像会更清晰.

普通矩阵的对角化·

这里说的“普通矩阵”,其实是指线性代数里学的R\mathbb{R}上的矩阵,它的对角化方法在[GSLA]的6.2节有说到;给定一个nnn*n的非奇异矩阵AA,要求它的对角矩阵Λ\Lambda和对角化变化矩阵XX的话:

  1. 先求出AA特征多项式pA(x)p_A(x),根据Cayley–Hamilton定理

    pA(λ)=det(AλInn)p_A(\lambda) = det(A-\lambda I_{n*n})

  2. 通过解pA(λ)=0p_A(\lambda)=0可以解出AAnn个特征值λ1,...,λn\lambda_1,...,\lambda_n,如果这nn个特征值各不相同的话,就直接有:

    Λ=(λ1000λ2000λn)\Lambda = \begin{pmatrix} \lambda_1 & 0 & \cdots & 0 \\ 0 & \lambda_2 & \cdots & 0 \\ \vdots & \vdots & \ddots & \vdots \\ 0 & 0 & \cdots & \lambda_n \\ \end{pmatrix}

  3. 接着解出nn个特征值对应的nn个特征向量x1,...,xnx_1,...,x_n,通过解方程:

    (AλiI)xi=0(A-\lambda_iI)x_i = 0

    可以解出各xix_i,然后就有:

    X=[x1,x2,...,xn]X = [x_1, x_2, ..., x_n]

    可以简单验算一下有:

    AX=XΛ=[λ1x1,λ2x2,...,λnxn]AX = X\Lambda = [\lambda_1x_1, \lambda_2x_2, ..., \lambda_nx_n]

    最后对角化其实就是:

    A=XΛX1A = X \Lambda X^{-1}

矩阵对角化的经典应用就是用来加快求幂:

Ak=XΛkX1A^k = X \Lambda^k X^{-1}

简要证明一下:原本AkA^k的话需要做kk次(优化后log(k)log(k)次)nnn*n的矩阵乘法,每次复杂度是O(n3)O(n^3),(优化后是O(n2+α)O(n^{2+\alpha}));对角化后的Λk\Lambda^k需要做kk次(优化后log(k)log(k)次)nnn*n对角矩阵的乘法,每次复杂度O(n)O(n),另外加两次矩阵乘法;所以在kk很大的情况下是可以大大提高运算速度的:

O(log(k))O(n)+2O(n2+α)O(nlog(k))O(log(k))*O(n)+2*O(n^{2+\alpha}) \approx O(n*log(k))

但是,在普通矩阵对角化的时候还有一些问题,比如:

  1. 特征多项式pA(x)=0p_A(x)=0无实数解的时候怎么办?
  2. 解出的特征值有重解(λi=λj\lambda_i=\lambda_j)的话怎么办?

域扩张(field extensions)·

对于第一个问题,可以用域扩张的方法,把实数域R\mathbb{R}扩张到复数域C\mathbb{C},即原来AA是在R\mathbb{R}上做运算的,现在改成在C\mathbb{C}上运算,那么pA(x)=0p_A(x)=0就总会有解,只不过有的可能是复数解(有ii),Λ\LambdaXX上也会有复数。

若尔当型(Jordan Form)·

对于第二个问题,可以用Jordan Form来解决;Jordan Form(Jordan Matrix)就是kk 个块(Jordan Block)组成的块对角矩阵,如:

(J1000J2000Jk)\begin{pmatrix} J_1 & 0 & \cdots & 0 \\ 0 & J_2 & \cdots & 0 \\ \vdots & \vdots & \ddots & \vdots \\ 0 & 0 & \cdots & J_k \\ \end{pmatrix}

其中,kk等于AA的所有特征值的几何重数之和,也有说是独立的特征向量(independent eigenvectors)个数;一般情况下是pA(λ)p_A(\lambda)的不同根的个数,但也有相同的根λi\lambda_i对应多个Jordan Block的情况(,好像还需要看(AλiI)(A-\lambda_iI)对应零空间的维度?),可以参考下这里的一个例子;

Jordan Matrix中,λi\lambda_i对应的一个JiJ_i长这样子:

(λi10λi1λi10λi)\begin{pmatrix} \lambda_i & 1 & & & 0 \\ & \lambda_i & 1 & & \\ & & \ddots & \ddots \\ & & & \lambda_i & 1 \\ 0 & & & & \lambda_i \\ \end{pmatrix}

即对角线上是λi\lambda_i,对角线上一个(叫上对角线?)是11;其中每一个JiJ_i的大小可以通过下面方式计算:

任何的非奇异方阵AA都会相似于一个Jordan矩阵JJ,即可以找到一个BB,使得:

A=BJB1A = BJB^{-1}

(顺便贴一下[GSLA] 8.3的说法,感觉比我说的简洁)

所以如果可以搞出JJ的话,也可以用类似对角化的方法加快幂运算:

Ak=BJkB1A^k = B J^k B^{-1}

其中JJ做了kk次方后仍然是只有“两条对角线”的的矩阵,即也是kk次(优化后log(k)log(k)次)O(n)O(n)的矩阵乘法;

JJ的求法可以参考知乎的一个专栏BB的求法可以参考维基百科;大概就是:

  1. pA(λ)p_A(\lambda)和各λi\lambda_i

  2. λi\lambda_i对应的Jordan块的阶数,根据阶数构造Jordan块,然后拼接Jordan矩阵;

  3. 如果JiJ_i的阶数为11的话,BB中对应那一列就是λi\lambda_i对应的特征向量,即计算

    (AλiI)bi=0(A-\lambda_iI)b_i = 0

  4. 如果JiJ_i阶数为l>1l>1的话,即可以算出BB中的ll个向量,其中第一个可以是λi\lambda_i的特征向量:

    (AλiI)bi,1=0(A-\lambda_iI)b_{i, 1} = 0

    然后其他剩余的l1l-1个向量可以通过解:

    (AλiI)bi,j=bi,j1(A-\lambda_iI)b_{i, j} = b_{i, j-1}

    得出(不过写代码时好像见到有无解的情况。。。

GL(n, p)上的对角化·

GL(n,Fp)GL(n, \mathbb{F}_p)上的对角化其实跟R\mathbb{R}上的差不多,只不过有一些需要注意的问题。

求特征值·

按上面的说法,给定一个矩阵AA,首先需要求它的特征多项式pA(λ)p_A(\lambda),求的方法是一样的,即解:

pA(λ)=det(AλI),  in Fpp_A(\lambda) = det(A-\lambda I),\ \ in\ \mathbb{F}_p

只不过运算是在域Fp\mathbb{F}_p上进行的;解特征值的方法也是一样的,即解:

pA(λ)=0,  in Fpp_A(\lambda)=0, \ \ in\ \mathbb{F}_p

然后这里就有了第一个问题:Fp\mathbb{F}_p上的方程怎么解

  1. 首先可以对pA(λ)p_A(\lambda)进行分解,比如用[Ber70]的方法,但后来发现其实用Sage就可以直接分解,只不过要把域定义好:

    1
    2
    3
    4
    5
    # Sage
    def factorFp(f, p):
    F.<a> = GF(p)
    R.<x> = PolynomialRing(F)
    return R(f).factor()
  2. 如果pA(λ)p_A(\lambda)可以被分解为若干个度(degree)为11的多项式(即最高次数项是λ\lambda)的乘积,那么方程就可以直接被解了;比如在F3\mathbb{F}_3上有:

    pA(λ)=λ3+λ2λ1=0,  in F3p_A(\lambda)=\lambda^3+\lambda^2-\lambda-1=0, \ \ in\ \mathbb{F}_3

    可以被分解为:

    (λ+2)(λ+1)2=0,  in F3(\lambda+2) * (\lambda+1)^2 = 0, \ \ in\ \mathbb{F}_3

    就可以解出:

    λ=1,2,2 ;  in F3\lambda = 1, 2, 2\ ; \ \ in\ \mathbb{F}_3

  3. 如果pA(λ)p_A(\lambda)不能完全被分解为degree为11的多项式(或者直接不能被分解)的话,则需要使用域扩张的方法,扩展到更大的域上;比如在F3\mathbb{F}_3上有:

    pA(λ)=λ3+λ2λ+1=0,  in F3p_A(\lambda)=\lambda^3+\lambda^2-\lambda+1=0, \ \ in\ \mathbb{F}_3

    F3\mathbb{F}_3pA(λ)p_A(\lambda)是不可约的,于是可以把F3\mathbb{F}_3扩张到:

    F33=F3[λ]/(λ3+λ2λ+1)\mathbb{F}_{3^3}=\mathbb{F}_3[\lambda]/(\lambda^3+\lambda^2-\lambda+1)

    F33\mathbb{F}_{3^3}即模pA(λ)p_A(\lambda)的多项式环,其中系数的运算是在F3\mathbb{F}_3上的,记F33\mathbb{F}_{3^3}上的变量是xx(只是个符号而已);扩张后可以得到pA(λ)p_A(\lambda)的三个不同解(原理未知,咕):

    λ=x30,x31,x32;  in F33\lambda=x^{3^0}, x^{3^1}, x^{3^2} ;\ \ in\ \mathbb{F}_{3^3}

    化简一下就是:

    λ=x,2x2+x+2,x2+x;  in F33\lambda = x, 2x^2+x+2, x^2+x ;\ \ in\ \mathbb{F}_{3^3}

  4. 如果pA(λ)p_A(\lambda)被分解成多个degree大于1的不可约多项式的话,λ\lambda好像就会分布在多个不同的域上;比如在F3\mathbb{F}_3上有:

    pA(λ)=λ4+1=0,  in F3p_A(\lambda)=\lambda^4+1=0, \ \ in\ \mathbb{F}_3

    pA(λ)p_A(\lambda)可以被分解为:

    pA(λ)=(λ2+λ+2)(λ2+2λ+2),  in F3p_A(\lambda) = (\lambda^2+\lambda+2) * (\lambda^2+2*\lambda+2), \ \ in\ \mathbb{F}_3

    λ\lambda就会分布在两个域上:F3[λ]/(λ2+λ+2)\mathbb{F}_3[\lambda]/(\lambda^2+\lambda+2)F3[λ]/(λ2+2λ+2)\mathbb{F}_3[\lambda]/(\lambda^2+2*\lambda+2)

    感觉上应该会有方法扩张到同一个更大的域上(咕

对角化·

对角化的方法基本上是一样的,特征值λi\lambda_i各不相同的话就可以生成对角矩阵Λ\Lambda,有重根的话就可以生成Jordan矩阵JJ;但像上面第4点说的,如果λ\lambda分布在不同的域上的话会有问题(咕

Demo Code:

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
# Sage
# 函数可以复制文章最下面的Demo Code

if __name__ == '__main__':
p = 33285073849485750791903437807279991921
m = matrix(IntegerModRing(p), [
[1, 1, 1],
[1, 0, 0],
[0, 1, 0]])
print(factorFp(m.charpoly(), p))
B, J, Bi = jordanDiagon(m, p)
print(p)
print(J)
print('--- ---')

q = 38845988283830087557982578789883120419
m = matrix(IntegerModRing(q), [
[1, 1, 1],
[1, 0, 0],
[0, 1, 0]])
print(factorFp(m.charpoly(), q))
B, J, Bi = jordanDiagon(m, q)
print(p)
print(J)

之所以是Demo Code是因为上面问题是没有解决的,即不是每个矩阵都能分解,但维度n3n \le 3的话是都适用的;用了一个series中的矩阵和ee分解的两个素数做例子,其中若p=33285073849485750791903437807279991921p=33285073849485750791903437807279991921的话,可以解出:

λ1=13232791622035946436448165355007395754,  in Fpλ2=x,  in Fp2λ3=33285073849485750791903437807279991920y+20052282227449804355455272452272596168,  in Fp2\lambda_1 = 13232791622035946436448165355007395754, \ \ in\ \mathbb{F}_p \\ \lambda_2 = x, \ \ in\ \mathbb{F}_{p^2} \\ \lambda_3 = 33285073849485750791903437807279991920*y + 20052282227449804355455272452272596168, \ \ in\ \mathbb{F}_{p^2} \\

然后就可以构造出对角矩阵:

Λ=(λ1000λ2000λ3)\Lambda= \begin{pmatrix} \lambda_1 & 0 & 0 \\ 0 & \lambda_2 & 0 \\ 0 & 0 & \lambda_3 \\ \end{pmatrix}

看上去同样是可以用这种方法,再求出BB,然后用BΛkB1B \Lambda^k B^{-1}加快求幂,但是要注意扩展域后是在Fp2\mathbb{F}_{p^2}上做的乘法运算;在最坏的情况下,是在Fpn\mathbb{F}_{p^n}上(即pA(λ)p_A(\lambda)是不可约的情况)做(nlog(k))(n*log(k))次乘法运算,一般的多项式乘法的复杂度是O(n2)O(n^2),使用FFT可优化到O(nlog(n))O(n*log(n));即求AAkk次幂可从原来的O(n2+αlog(k))O(n^{2+\alpha}*log(k))优化到:

O(n2log(n)log(k))O(n^2*log(n)*log(k))

所以其实这种优化是比较“看脸”的,而且据说已经有矩阵乘法的方法可以做到O(n2log(n))O(n^2*log(n))了[Wied14],不过如果pA(λ)p_A(\lambda)是可约的话还是可以优化一下的(

扩展·

既然现在知道GL(n,Fp)GL(n, \mathbb{F}_p)的对角化方法了,那么就进一步解释上次计算GL(n,Fp)GL(n, \mathbb{F}_p)的阶的证明了(大概讲一下成因,详细的证明不会写,咕);另外这里也提供一种生成GL(n,Fp)GL(n, \mathbb{F}_p)的“最大阶”(pn1p^n-1)的方法:

证明的进一步解释·

首先说一下“最大阶”的证明,当时的说法是:对于AGL(n,Fp)A \in GL(n, \mathbb{F}_p)的话,最大的AA的阶是pn1p^n-1,原因是pA(λ)p_A(\lambda)最多nn项,减去全00项的话最多就有pn1p^n-1种特征多项式;当时论坛的那段回复后面还有一段:

大概意思就是pA(λ)p_A(\lambda)的解(在阶最大的情况?)是在扩张后的域Fpn\mathbb{F}_{p^n}上的,那么对角矩阵/Jordan矩阵的运算是在Fpn\mathbb{F}_{p^n}上的,先看一下比较简单的对角矩阵的情况:由于Fpn\mathbb{F}_{p^n}的阶就是pn1p^n-1,所以Λ\Lambda在做pn1p^n-1次自乘后对角线上所有元素就会变为11,即:

Λpn1=I ,    in Fpn\Lambda^{p^n-1} = I\ , \ \ \ \ in\ \mathbb{F}_{p^n}

Jordan矩阵的情况会比较复杂,经实验(懒得证了-),JJ的上对角线上有11的话,阶上就会产生因子pp,在奇素数的情况下应该可以证到阶会比pn1p^n-1小的?(注意如果pA(λ)p_A(\lambda)不可约的话特征值是nnFpn\mathbb{F}_{p^n}上的不同值), 咕

说一下阶的证明,当时的说法是:

Apri=1nϕi(p)I  (in GLn(Zp))A^{p^r\prod_{i=1}^{n}\phi_i(p)} \equiv I \ \ (in\ GL_n(\mathbb{Z}_p))

先看ϕi(p)\phi_i(p)的那nn个因子,假设pA(λ)p_A(\lambda)可以被分解为(ni)(n-i)个degree为11的多项式和剩余的11个不可约多项式,那么AA的特征值就有(ni)(n-i)个在Fp\mathbb{F}_{p}上和ii个在Fpi\mathbb{F}_{p^i}上,于是如果是特征值互不相等的情况的话,就有:

Λpi1=I ,    in Fpi\Lambda^{p^i-1} = I\ , \ \ \ \ in\ \mathbb{F}_{p^i}

(注意pi1p^i-1含因子p1p-1,也可看成Fp\mathbb{F}_{p}可以被扩张到Fpi\mathbb{F}_{p^i});而ϕi(p)\phi_i(p)其实是pip^i的因子,所以就会有i=1nϕi(p)\prod_{i=1}^{n}\phi_i(p)这一块;

如果AA的特征值互不相等的话,就是大概对角化到一个Jordan矩阵,会产生若干的pp的因子,至于为什么是prp^r可以看下[MW97]的第三章。。。

最大阶的生成元·

AGL(n,Fp)A \in GL(n, \mathbb{F}_p)的阶其实是和它的对角矩阵/Jordan矩阵的阶是一样的,按上面的说法,如果想达到最大阶的话,可以构造一个对角矩阵Λ\Lambda,其元素都是Fpn\mathbb{F}_{p^n}上的,而且至少一个是Fpn\mathbb{F}_{p^n}的原根,那么Λ\Lambda的阶就是pn1p^n-1,即AA的阶也是pn1p^n-1

根据上面的说法,pA(λ)p_A(\lambda)应该是一个Fp\mathbb{F}_p上不可约的多项式(而且pA(λ)=0p_A(\lambda)=0至少有一个解是Fpn\mathbb{F}_{p^n}的原根),在假设能找到这样的pA(λ)p_A(\lambda)的前提下,可以通过以下方法找到GL(n,Fp)GL(n, \mathbb{F}_p)中一个阶为pn1p^n-1的元素:

  1. 构造pA(λ)p_A(\lambda)伴随矩阵,记为CC

  2. 选取随机RGL(n,Fp)R \in GL(n, \mathbb{F}_p),即可构造:

    A=RCR1A = RCR^{-1}

  3. 根据伴随矩阵的性质,CCnn个特征值就是pA(λ)p_A(\lambda)对应的nn个解;根据上面说法,CC的阶是pn1p^n-1ACA≌CAA的阶和CC相同,也为pn1p^n-1

Demo Code·

贴一个不太完善的参考代码:

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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# Sage
from collections import Counter

var('x')
def factorFp(f, p):
F.<a> = GF(p)
R.<x> = PolynomialRing(F)
return R(f).factor()

def isIrreducible(f, p):
ft = factorFp(f, p)
return len(ft)==1 and ft[0][1]==1

# Actually, m.charpoly()
def getCharPoly(m):
mm = m.change_ring(ZZ).change_ring(SR) # Fp -> SR
I = identity_matrix(mm.ncols())
return (mm-x*I).det().expand()

def getLambdas(m, p):
#cpm = getCharPoly(m)
cpm = m.charpoly()
fs = factorFp(cpm, p)
F.<a> = GF(p)
R.<x> = PolynomialRing(F)

#lambdas = {}
lambdas = {F: []} # always F first ?
try:
for f in fs:
for i in range(f[1]):
deg = f[0].degree()
if deg == 1:
lam = F(-(f[0](x=0)))
try:
lambdas[F].append(lam)
except:
lambdas[F] = [lam]
else:
S = R.quotient(f[0], 'y')
lambdas[S] = []
lam = S(x)
for d in range(deg):
if d != 0:
lam = power(lam, p)
#lam = S(x^(p^d))
lambdas[S].append(lam)
return lambdas
except Exception as e:
# the right cpm will never get here ?
print('???')
print(e)
exit(0)

# https://zhuanlan.zhihu.com/p/337122096
def jordanDiagon(m, p): # default:right
n = m.ncols()
lambdas = getLambdas(m, p)

if len(lambdas) > 2:
raise NotImplementError("I don't know how to do that now ...")

S = list(lambdas.keys())[-1]
lams = []
for Si in lambdas:
lams += [S(x) for x in lambdas[Si]]
lambdas = lams
lambdas.sort()
lams = Counter(lambdas)

I = identity_matrix(S, n)
Js = []
evs = [] # eigenvectors
for lam in lams.keys():
numLam = lams[lam]
m1 = m-lam*I
numBlock = n - m1.rank()

# get ri (rank is n for I)
rs = [n]
i = 1
while True:
ri = (m1^i).rank()
if ri == rs[-1]:
rs.append(ri)
break
rs.append(ri)
i += 1

# get orders
jb = {}
for i in range(len(rs))[1:-1]:
tmp = rs[i-1] + rs[i+1] - 2*rs[i]
if tmp != 0:
jb[i] = tmp

# there are k blocks with order d
for di in range(len(jb.keys())):
d = list(jb.keys())[di]
for k in range(jb[d]):
# for B
ns = m1.right_kernel(basis='pivot') # not work for reducible S ...
if d==1:
nb = ns.basis()
evs.append(nb[di])
else: # TODO: no solution ???
v0 = vector([0 for _ in range(n)])
while True:
ev0 = ns.random_element()
if ev0==v0 or ev0 in evs:
continue
tmp = [ev0]
try:
for i in range(1, d):
tmp.append(m1 \ vector(tmp[-1]))
#print(tmp)
except:
continue
break
evs += tmp

# for J
Ji = diagonal_matrix(S, [lam for _ in range(d)])
jpos = 0
for j in range(d):
if j != d-1:
Ji[jpos+j, jpos+j+1] = 1
Js.append(Ji)
jpos += d

# Jordan block -> Jordan matrix
#J = block_diagonal_matrix(S, Js) # TypeError: Not a scalar type.
J = zero_matrix(S, n)
pos = 0
for Ji in Js:
ni = Ji.ncols()
for ri in range(ni):
for ci in range(ni):
tmp = Ji[ri, ci]
if tmp != 0:
J[pos+ri, pos+ci] = tmp
pos += ni

B = matrix(S, evs).transpose()
Binv = B.inverse()

return B, J, Binv

def randMp(p, n):
while True:
m = matrix(IntegerModRing(p), [[randrange(p) for _ in range(n)] for _ in range(n)])
if m.det()!=0:
return m

# MA p194
# companion matrix of a polynomial (eigen values == roots of cp)
def companMp(cp, p):
F.<a> = GF(p)
R.<x> = PolynomialRing(F)
f = R(cp)
n = f.degree()
m = zero_matrix(F, n)
for i in range(1, n):
m[i, i-1] = 1
for i in range(n):
m[i, n-1] = -f[i]
return m

参考·

  1. [GSLA] Introduction to Linear Algebra, Fifth Edition (2016), http://math.mit.edu/~gs/linearalgebra/
  2. [Ber70] Berlekamp E R. Factoring polynomials over large finite fields[J]. Mathematics of computation, 1970, 24(111): 713-735.
  3. [MW97] Menezes A J, Wu Y H. The discrete logarithm problem in GL (n, q)[J]. Ars Combinatoria, 1997, 47: 23-32.
  4. [Wied14] Wiedermann J. Fast nondeterministic matrix multiplication via derandomization of Freivalds’ algorithm[C]//IFIP International Conference on Theoretical Computer Science. Springer, Berlin, Heidelberg, 2014: 123-135.
  5. [MA] Horn R A, Johnson C R. Matrix analysis[M]. Cambridge university press, 2012.