积和式(permanent)是一种和行列式长得很像的矩阵函数。在介绍积和式之前,我们先看看行列式(determinant)的定义。
首先需要引入“排列”(permutation)的概念。对于集合 S = { 1 , 2 , ⋯ , n } S=\{1,2,\cdots,n\} S={1,2,⋯,n},它的一个排列 σ \sigma σ就是对 S S S中元素的一个重排。 σ \sigma σ的第 i i i个元素记作 σ i \sigma_i σi。例如,对于 n = 5 n=5 n=5,我们令 σ = { 2 , 5 , 1 , 4 , 3 } \sigma=\{2,5,1,4,3\} σ={2,5,1,4,3},则 σ 3 = 1 \sigma_3=1 σ3=1, σ 5 = 3 \sigma_5=3 σ5=3。
排列的逆序对就是 a a a在 b b b前面但 σ a > σ b \sigma_a>\sigma_b σa>σb的情况。例如 σ = { 2 , 1 , 3 , 5 , 4 } \sigma=\{2,1,3,5,4\} σ={2,1,3,5,4},有两个逆序对: ( σ 1 , σ 2 ) = ( 2 , 1 ) (\sigma_1,\sigma_2)=(2,1) (σ1,σ2)=(2,1)和 ( σ 4 , σ 5 ) = ( 5 , 4 ) (\sigma_4,\sigma_5)=(5,4) (σ4,σ5)=(5,4)。一个排列 σ \sigma σ中逆序对的个数记作 τ ( σ ) \tau(\sigma) τ(σ)。令 s g n ( σ ) = ( − 1 ) τ ( σ ) \mathrm{sgn}(\sigma)=(-1)^{\tau(\sigma)} sgn(σ)=(−1)τ(σ)。对于一个排列 σ \sigma σ,如果你把其中的两个数互换,则 s g n ( σ ) \mathrm{sgn}(\sigma) sgn(σ)会变号。所有 n n n个元素的排列的集合记作 S n S_n Sn。例如, S 3 = { ( 1 2 3 ) , ( 1 3 2 ) , ( 2 1 3 ) , ( 2 3 1 ) , ( 3 1 2 ) , ( 3 2 1 ) } S_3=\{(1\ 2\ 3),(1\ 3\ 2),(2\ 1\ 3),(2\ 3\ 1),(3\ 1\ 2),(3\ 2\ 1)\} S3={(1 2 3),(1 3 2),(2 1 3),(2 3 1),(3 1 2),(3 2 1)}。
给定一个 n × n n\times n n×n的矩阵 A = ( a i j ) n × n A=(a_{ij})_{n\times n} A=(aij)n×n,它的行列式为 det ( A ) = ∑ σ ∈ S n ( s g n ( σ ) ∏ i = 1 n a i , σ i ) \det(A)=\sum\limits_{\sigma\in S_n}\left(\mathrm{sgn}(\sigma)\prod\limits_{i=1}^{n}a_{i,\sigma_{i}}\right) det(A)=σ∈Sn∑(sgn(σ)i=1∏nai,σi)例如,当 n = 3 n=3 n=3时,设 A = [ a b c d e f g h i ] A=\begin{bmatrix}a&b&c\\d&e&f\\g&h&i\end{bmatrix} A= adgbehcfi ,则 det ( A ) = a e i − a f h + b f g − b d i + c d h − c e g \det(A)=aei-afh+bfg-bdi+cdh-ceg det(A)=aei−afh+bfg−bdi+cdh−ceg而积和式的定义就是在行列式中把 s g n ( σ ) \mathrm{sgn}(\sigma) sgn(σ)去掉: p e r m ( A ) = ∑ σ ∈ S n ( ∏ i = 1 n a i , σ i ) \mathrm{perm}(A)=\sum\limits_{\sigma\in S_n}\left(\prod\limits_{i=1}^{n}a_{i,\sigma_{i}}\right) perm(A)=σ∈Sn∑(i=1∏nai,σi)可以理解为:在矩阵中每行选取一个元素,且要求这些元素的列各不相同;将这些元素乘起来,得到一个乘积,积和式就是所有可能的选法对应的乘积之和。例如,当 n = 3 n=3 n=3时,设 A = [ a b c d e f g h i ] A=\begin{bmatrix}a&b&c\\d&e&f\\g&h&i\end{bmatrix} A= adgbehcfi ,则 p e r m ( A ) = a e i + a f h + b f g + b d i + c d h + c e g \mathrm{perm}(A)=aei+afh+bfg+bdi+cdh+ceg perm(A)=aei+afh+bfg+bdi+cdh+ceg积和式在量子场论、图论等领域中有应用。
积和式与行列式看起来只是某些项的符号不同,而且积和式看起来更简单了(没有 s g n ( σ ) \mathrm{sgn}(\sigma) sgn(σ)),那是不是比行列式好算呢?答案是:大错特错!行列式可以用高斯消元法在 O ( n 3 ) O(n^3) O(n3)的时间内算出来,而积和式目前最快的算法需要指数级的时间。事实上,1979年,Leslie G. Valiant证明了积和式的计算是 # P \mathsf{\# P} #P完全问题,如果发现积和式有多项式时间的算法,那么将意味着 F P = # P \mathsf{FP}=\mathsf{\#P} FP=#P,这是比 P = N P \mathsf{P}=\mathsf{NP} P=NP还要强的命题。而大多数计算机科学家认为 P ≠ N P \mathsf{P}\ne\mathsf{NP} P=NP,所以积和式大概率没有多项式时间的算法。我们要介绍的Ryser算法就是 O ( n 2 n ) O(n 2^n) O(n2n)时间的。
Ryser算法的核心思想就是容斥原理。我们还是先考察一下 n = 3 n=3 n=3的情况:令 A = [ a b c d e f g h i ] A=\begin{bmatrix}a&b&c\\d&e&f\\g&h&i\end{bmatrix} A= adgbehcfi ,则 p e r m ( A ) = a e i + a f h + b f g + b d i + c d h + c e g \mathrm{perm}(A)=aei+afh+bfg+bdi+cdh+ceg perm(A)=aei+afh+bfg+bdi+cdh+ceg观察式子 T = ( a + b + c ) ( d + e + f ) ( g + h + i ) T=(a+b+c)(d+e+f)(g+h+i) T=(a+b+c)(d+e+f)(g+h+i),你会发现它的展开式中包含积和式的 6 6 6个项(用蓝色标出): T = a d g + a d h + a d i + a e g + a e h + a e i + a f g + a f h + a f i + b d g + b d h + b d i + b e g + b e h + b e i + b f g + b f h + b f i + c d g + c d h + c d i + c e g + c e h + c e i + c f g + c f h + c f i \begin{aligned} T&=a d g + a d h + a d i + a e g + a e h + \textcolor{blue}{a e i} + a f g + \textcolor{blue}{a f h} + a f i\\ &+b d g + b d h + \textcolor{blue}{b d i} + b e g + b e h + b e i + \textcolor{blue}{b f g} + b f h + b f i\\ &+c d g + \textcolor{blue}{c d h} + c d i + \textcolor{blue}{c e g} + c e h + c e i + c f g + c f h + c f i \end{aligned} T=adg+adh+adi+aeg+aeh+aei+afg+afh+afi+bdg+bdh+bdi+beg+beh+bei+bfg+bfh+bfi+cdg+cdh+cdi+ceg+ceh+cei+cfg+cfh+cfi于是,我们只需要在 T T T的展开式中剔除不属于积和式的项就可以了。不属于积和式的项,也就是选取的某两个元素在同一列的项。这些项的特点是:元素的列组成的集合大小不超过 2 2 2。比如 a d h adh adh一项,它只涉及第一和第二列,而没有涉及第三列,所以它不是积和式中的项。同样, c f i cfi cfi只涉及第三列,它也不是积和式中的项。我们可以枚举元素的列组成的集合(集合的大小为 2 2 2),将对应的项剔除出去。
只需要从 T T T中把这些项剔除出去就可以了。但答案是 p e r m ( A ) = T − H 12 − H 23 − H 13 \mathrm{perm}(A)=T-H_{12}-H_{23}-H_{13} perm(A)=T−H12−H23−H13吗?非也,因为 H 12 H_{12} H12、 H 23 H_{23} H23、 H 13 H_{13} H13之间还有重叠部分,我们减的时候把重叠部分减了两次,还得加回来。 H 12 H_{12} H12和 H 23 H_{23} H23的重叠部分,就是只涉及第二列的项: b e h beh beh。 H 12 H_{12} H12和 H 13 H_{13} H13的重叠部分则是只涉及第一列的项: a d g adg adg。同理, H 23 H_{23} H23和 H 13 H_{13} H13的重叠部分就是只涉及第三列的项—— c f i cfi cfi了。
这样,我们得到计算三阶矩阵积和式的公式为: p e r m ( A ) = T − H 12 − H 23 − H 13 + a d g + b e h + c f i = ( a + b + c ) ( d + e + f ) ( g + h + i ) − ( a + b ) ( d + e ) ( g + h ) − ( b + c ) ( e + f ) ( h + i ) − ( a + c ) ( d + f ) ( g + i ) + a d g + b e h + c f i \begin{aligned} \mathrm{perm}(A)&=T-H_{12}-H_{23}-H_{13}+adg+beh+cfi\\ &=(a+b+c)(d+e+f)(g+h+i)-(a+b)(d+e)(g+h)-(b+c)(e+f)(h+i)-(a+c)(d+f)(g+i)+adg+beh+cfi \end{aligned} perm(A)=T−H12−H23−H13+adg+beh+cfi=(a+b+c)(d+e+f)(g+h+i)−(a+b)(d+e)(g+h)−(b+c)(e+f)(h+i)−(a+c)(d+f)(g+i)+adg+beh+cfi我们可以把这种容斥原理的思想推广到 n n n阶矩阵的积和式。计算 n n n阶矩阵的积和式的Ryser公式如下: p e r m ( A n × n ) = ( − 1 ) n ∑ S ⊆ { 1 , 2 , ⋯ , n } [ ( − 1 ) ∣ S ∣ ∏ i = 1 n ( ∑ j ∈ S a i j ) ] \mathrm{perm}(A_{n\times n})={(-1)}^{n} \sum\limits_{S\subseteq \{1,2,\cdots,n\}}\left[{(-1)}^{|S|}\prod\limits_{i=1}^{n}\left(\sum\limits_{j\in S}a_{ij}\right)\right] perm(An×n)=(−1)nS⊆{1,2,⋯,n}∑ (−1)∣S∣i=1∏n j∈S∑aij 这个公式可以这么理解:我们把 A A A的行和之积展开,里面一定包含我们要求的积和式;然后减去涉及 n − 1 n-1 n−1列的项,加上涉及 n − 2 n-2 n−2列的项,减去涉及 n − 3 n-3 n−3列的项,……式中 S S S就是涉及的列的集合, ( − 1 ) ∣ S ∣ (-1)^{|S|} (−1)∣S∣用于计算是加还是减;前面的 ( − 1 ) n {(-1)}^{n} (−1)n是修正项,用于解决当 n n n是奇数时, S = { 1 , 2 , ⋯ , n } S=\{1,2,\cdots,n\} S={1,2,⋯,n}的情况下 ( − 1 ) ∣ S ∣ {(-1)}^{|S|} (−1)∣S∣是负数的问题。
理论上讲,如果我们按照格雷码顺序枚举 S S S,那么时间复杂度可以降到 O ( n 2 n ) O(n2^n) O(n2n)。但在这里我们为了方便起见就递归枚举 S S S,对于每个 S S S,计算各行的、列号为 S S S的元素之和的乘积即可。下面给出一个时间复杂度为 O ( n 2 2 n ) O(n^2 2^n) O(n22n)的C++实现:
#include
typedef std::int64_t num;
num recursion(int i, bool* b, int n, num** A)
// 枚举S
{
if(i == n) // 递归终点,已经得到一个S
{
num prod = 1;
for(int row = 0; row < n; row++)
{
num sum = 0;
for(int col = 0; col < n; col++)
{
if(b[col])
{
sum += A[row][col];
}
}
prod *= sum;
}
int S_size = 0; // |S|
for(int col = 0; col < n; col++)
{
if(b[col])
{
S_size++;
}
}
if(S_size % 2 == 1) // (-1)^|S|
{
prod = -prod;
}
return prod;
}
num result = 0;
b[i] = true; // 选第i列
result += recursion(i + 1, b, n, A);
b[i] = false; // 不选第i列
result += recursion(i + 1, b, n, A);
return result;
}
num ryser(int n, num** A)
// 计算n x n矩阵A的积和式
{
bool* b = new bool[n]; // S中是否含有第i列
num result = recursion(0, b, n, A);
delete []b;
if(n % 2 == 1)
{
result = -result; // (-1)^n
}
return result;
}