【线性代数/计算复杂性理论】积和式的指数时间算法:Ryser算法

一、积和式的定义

积和式(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=1nai,σ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)=aeiafh+bfgbdi+cdhceg而积和式的定义就是在行列式中把 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=1nai,σ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算法

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),将对应的项剔除出去。

  • 只涉及第一、二列的项: H 12 = ( a + b ) ( d + e ) ( g + h ) = a d g + a d h + a e g + a e h + b d g + b d h + b e g + b e h H_{12}=(a+b)(d+e)(g+h)=a d g + a d h + a e g + a e h + b d g + b d h + b e g + b e h H12=(a+b)(d+e)(g+h)=adg+adh+aeg+aeh+bdg+bdh+beg+beh
  • 只涉及第二、三列的项: H 23 = ( b + c ) ( e + f ) ( h + i ) = b e h + b e i + b f h + b f i + c e h + c e i + c f h + c f i H_{23}=(b+c)(e+f)(h+i)=b e h + b e i + b f h + b f i + c e h + c e i + c f h + c f i H23=(b+c)(e+f)(h+i)=beh+bei+bfh+bfi+ceh+cei+cfh+cfi
  • 只涉及第一、三列的项: H 13 = ( a + c ) ( d + f ) ( g + i ) = a d g + a d i + a f g + a f i + c d g + c d i + c f g + c f i H_{13}=(a+c)(d+f)(g+i)=a d g + a d i + a f g + a f i + c d g + c d i + c f g + c f i H13=(a+c)(d+f)(g+i)=adg+adi+afg+afi+cdg+cdi+cfg+cfi

只需要从 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)=TH12H23H13吗?非也,因为 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)=TH12H23H13+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)Si=1n jSaij 这个公式可以这么理解:我们把 A A A的行和之积展开,里面一定包含我们要求的积和式;然后减去涉及 n − 1 n-1 n1列的项,加上涉及 n − 2 n-2 n2列的项,减去涉及 n − 3 n-3 n3列的项,……式中 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 <cstdint>

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;
}

猜你喜欢

转载自blog.csdn.net/qaqwqaqwq/article/details/129223226