冷滟泽的个人博客冷滟泽的个人博客

对称群上傅里叶变换的快速计算方法

一. 线性代数与群表示理论基础

【定义 1.1】(线性空间)

数域 K 上的线性空间 V 是一个向量集合,该集合定义了加法和数乘两种运算,且集合 V 在加法运算下构成阿贝尔群。

【定义 1.2】(线性无关与维数)

线性空间 V 中,任意 n 个向量 \vec{x_1},\vec{x_2},\cdots,\vec{x_n},其线性组合 a_1\vec{x_1}+a_2\vec{x_2}+\cdots a_n\vec{x_n}=0 当且仅当 a_1=a_2=\cdots a_n=0 时成立,则称此 n 个向量线性无关,否则它们线性相关。线性空间中线性无关向量的最大个数称为空间 V 的维数,记为 \dim V

【定义 1.3】(基矢)

Vn 维线性空间,则 V 中任意一组 n 个线性无关向量,称为空间 V 的基矢,记为 (\vec{e_1},\vec{e_2},\cdots,\vec{e_n})。空间中任意矢量均可表示为 n 个基矢的线性组合。

【定义 1.4】(线性变换)

线性变换 A 是将 V 映入 V 的线性映射,满足 \forall\vec{x},\vec{y}\in V,A(\vec{x}+\vec{y})=A(\vec{x})+A(\vec{y})。线性变换可以在指定基底 e=(e_1,e_2\cdots,e_n) 下写成 \dim V 阶方阵的形式,作用一个线性变换即相当于左乘 eAe^{-1}。若 \det A\neq 0,则称线性变换 A 非奇异,此时 A 有逆变换 A^{-1}

【定义 1.5】(线性变换群)

定义两个变换的乘法为两个线性变换的相继作用(即矩阵乘法),则 n 维线性空间 V 上全部非奇异线性变换构成的集合在此乘法下构成一个群,称为 n 维一般线性群,记为 GL(V),其子群 L(V) 称为 V 上的线性变换群。

【定义 1.6】(群表示)

设有群 G,如果存在一个从 G 到线性空间 V 上的线性变换群 L(V) 的同态映射 \rho,则同态映射 \rho 称为群 G 的一个线性表示,V 为表示空间,V 的维数称为表示的维数,记为 d_\rho

【定义 1.7】(等价表示)

设群 G 在表示空间 V 中取基 (e_1,e_2,\cdots,e_n) 下的表示为 \rho(g),在另一组基 (e'_1,e'_2,\cdots,e'_n) 下的表示为 \rho'(g),若 \vec{e}'=\vec{e}XX 为两组基之间的变换,有 \rho'(g)=X^{-1}\rho(g)X,\det X\neq 0,则称表示 \rho,\rho' 等价,或 \rho'\rho 的等价表示。

【定义 1.8】(不可约表示)

\rho 是群 G 在表示空间 V 上的一个表示,V 如果不存在 G 不变的非平凡子空间 W\subset V,W\neq 0,W\neq V,\forall g\in G,\vec{x}\in W,\rho(g)\vec{x}\in W,此时称 \rhoG 的一个不可约表示。下记群 G 所有不等价不可约表示构成的集合为 \mathcal{R}(G)

【定义 1.9】(线性空间的直和)

设线性空间 V 有子空间 W_1W_2W_1\cap W_2=0。对于任意 \vec{x}\in V,可找到 \vec{x_1}\in W_1,\vec{x_2}\in W_2,并唯一地将 \vec{x} 表示为 x=x_1+x_2,则称线性空间 V 是子空间 W_1W_2 的直和,记为 V=W_1\oplus W_2

二. 总体思路

【定理 2.1】(勃恩赛德(Burnside)定理)有限群的所有不等价不可约表示维数的平方和等于群的阶,即:

\sum_{\rho\in\mathcal{R}(G)}d_\rho^2=|G|.

这个等式说明了将一个群写为其所有不等价不可约表示的矩阵形式时数据量仍是 |G|,因此它奠定了下面所有推导的基础。

【定义 2.2】(有限群傅里叶变换)

函数 f:G\to \mathbf{C} 的傅里叶的傅里叶变换定义为一组矩阵

\hat{f}(\rho)=\sum_{s\in G}f(s)\rho(s)\qquad\rho\in\mathcal{R}(G)\tag{2.1}.

其逆变换为

f(s)=\frac{1}{|G|}\sum_{\rho\in\mathcal{R}(G)}d_\rho\operatorname{Tr}\{\rho(s^{-1})\hat{f}(\rho)\}\qquad s\in G\tag{2.2}.

可以看出傅里叶变换 f\mapsto\hat{f} 是一个从 \mathbb{C}^{n!} 映入 \bigoplus_{\rho\in\mathcal{R}(G)}\mathbb{C}^{d_\rho\times d_\rho} 的线性变换,其中 \mathbb{C}^{d_\rho\times d_\rho} 就是表示 \rho 映射的矩阵线性空间。

和通常一样,这种变换可以将卷积化为点乘:

\widehat{f*g}(\rho)=\hat{f}(\rho)\hat{g}(\rho)\qquad\rho\in\mathcal{R}(G)\tag{2.3}.

其中卷积定义为 (f*g)(s)=\sum_{t\in G}f(st^{-1})g(t)。点乘大大减小了乘法的代价,但暴力实现的傅里叶变换的复杂度显然是 O(|G|^2)(甚至更高)的。我们考虑借鉴 DFT 的思路,使用分治的方法将问题规模减小,以提高效率。取 G 的一个真子群 H,令 k=|G|/|H|,则 (2.1) 可以写为

\hat{f}(\rho)=\sum_{i=1}^k\rho(s_i)\sum_{h\in H}f_i(h)\rho(h)\tag{2.4},

其中 f_i(h)=f(s_ih)。这个式子的内项和已经很像是定义在群 H 上的函数 f_i 的傅里叶变换的形式,不过表示 \rho 对于子群 H 来说通常是不可约的(但一定也是 H 的一个表示)。根据【定义 1.8】,此时 \rho 的表示空间 V 可以写成若干个线性空间 V_i 的直和:

V=V_1\oplus V_2\oplus\cdots\oplus V_r

使得 \rho 在每个 V_i 空间下都是 H 的不可约表示。也就是说,存在一组合适的基底,使得 \rho(h) 可以写成以下分块矩阵的形式:

\begin{pmatrix}\rho'_1(h)&&\\&\ddots&\\&&\rho'_r(h)\end{pmatrix},\rho'_i\in\mathcal{R}(H),1\leq i\leq k\tag{2.5}.

综上所述,当表示空间基底合适时,所有的 \hat{f_i}(\rho') 之间通过重组决定了 (2.4) 式的内项和。这样我们就把原问题拆解成了若干个规模更小的子问题。假设子群 H 的大小可以取到最优情况,设两个 d 阶方阵相乘的复杂度为 O(d^3),则这样不断迭代下去的最坏情况复杂度为 O(|G|^{1.5+\epsilon})。若取矩乘复杂度为理论的 O(d^2),则可以做到 O(|G|\log|G|)。这是面对一般群的通用方法,而实际应用时则要根据指定群的性质来选择子群并优化复杂度。

三. 对称群 S_n 的不可约表示

对称群 S_n 是在所有 n 阶置换构成的集合上定义了置换乘法的一个群。因此 S_nn! 个元素,并且它在 n>2 时不是阿贝尔群。对称群的表示有一个很奇妙的性质:S_n 的不可约表示与 n 的划分 \lambda(记为 \lambda\vdash n)之间存在意义对应的关系。这里的 \lambda=(\lambda_1,\lambda_2,\cdots,\lambda_r),满足 \lambda_1\geq\lambda_2\geq\cdots\lambda_r>0\lambda_1+\lambda_2+\cdots+\lambda_r=nn 的每个划分 \lambda 可以用一个 n 阶杨图(Young diagram)表示,\lambda_i 表示第 i 行的格子数。比如 \lambda=(3,2,2,1,1) 对应的杨图为:

如在第 2 节中描述的,我们在算法中使用一系列嵌套子群

S_n\supset S_{n-1}\supset\cdots\supset S_1=\{id\},

其中 S_{n-j}=\{\pi\in S_n\mid\pi(n)=n,\cdots,\pi(n-j+1)=n-j+1\},即固定后 j 个位置不变的置换群。记 [\![p,q]\!]\in S_n 为置换

[\![p,q]\!](i)=\left\{\begin{matrix}{}i+1&\text{for}\;\;p\leq i\leq q-1\\p&\text{for}\;\;i=q\\i&\text{otherwise}\end{matrix}\right.\qquad 1\leq p\leq q\leq n.

换句话说,[\![p,q]\!] 是将 p+1,p+2,\cdots,q 向左循环移动一位的置换。[\![p,q]\!] 容易分解为 q-p-1 个交换相邻项的置换的乘积:

[\![p,q]\!]=(p,p+1)\cdots(q-2,q-1)(q-1,q).

我们取 s_i=[\![i,n]\!],这样显然从 \{s_i\mid 1\leq i\leq n\}S_{n-1} 中分别取一个元素的乘积恰好取遍了 S_n 的每一个元素。

另一方面,分支定理说明了 S_n 的不可约表示 \rho 是如何分解为 S_{n-1} 的不可约表示的。

【定理 3.1】(分支定理(Branching theorem))

\rhoS_n 的一个不可约表示,对应着 n 的一个划分 \lambda。那么 \rhoS_{n-1} 限制下分解为一系列对应着划分 \lambda'\vdash n-1 的不可约表示的直和,而 \lambda' 可以由 \lambda 以所有合法的方式删去一个格子得到。

举个例子,令 \lambda=(3,2,2,1,1)。那么 \rho_\lambdaS_{8} 的限制下分裂为 \rho_{(2,2,2,1,1)}\rho_{(3,2,1,1,1)}\rho_{(3,2,2,1)}

这意味着对于 \pi\in S_{n-1}\rho(\pi) 在合适的基底下可以写成 (2.5) 分块矩阵的形式。记 \lambda 删去一个格子得到的划分集合为 R(\lambda),则 (2.4) 在对称群下可以进一步化为

\hat{f}(\lambda)=\sum_{i=1}^{n}\rho_\lambda([\![i,n]\!])\bigoplus_{\lambda'\in R(\lambda)}\hat{f_i}(\lambda')\tag{3.1}.

至于整数划分如何对应对称群的不可约表示,以及取什么样的基底能使 (3.1) 成立,请看下一节。

四. 杨氏半正规表示

将正整数 1\sim n 不重复地填入 n 阶杨图的每个格子,可以得到一张 n 阶杨表(Young tableau)。如果一张杨表上的整数从上到下,从左到右递增,则称它是标准的。例如 \lambda=(3,2) 时,对应的所有标准杨表为

\begin{matrix}1&3&5\\2&4\end{matrix}\qquad \begin{matrix}1&2&5\\3&4\end{matrix}\qquad \begin{matrix}1&3&4\\2&5\end{matrix}\qquad \begin{matrix}1&2&4\\3&5\end{matrix}\qquad \begin{matrix}1&2&3\\4&5\end{matrix}\qquad

【定义 4.1】(末标号序)

标准杨表的末标号序(last letter order)一种线性序,其定义如下:考虑两个相同形状的 n 阶杨表 T_1T_2。若 nT_1 中所在行比 T_2 高,则 T_1<T_2;若 nT_1T_2 中位于同一行,则同时删掉标号为 n 的格子并比较剩下的 n-1 阶杨表。上面例子所示的五个杨表就是如此的递增排序的。

【定义 4.2】(轴向距离)

对于一张杨表(不一定是标准的) T 上的标号 a\in\{1,2,\cdots,n\},记 r_T(a)a 所在行的编号,c_T(a)a 所在列的编号。给定两个标号 a,b\;\{1\leq a,b\leq n\},定义 ab 的轴向距离(axial distance)为

d^T(a,b)=(c_T(a)-c_T(b))+(r_T(b)-r_T(a)).

如果认为往左下走的贡献为正,往右上走的贡献为负,容易发现这是 ab 所需的步数。例如,令 T=\begin{smallmatrix}1&2&3\\5&4\end{smallmatrix},则 d^T(3,5)=3,d^T(4,1)=0

【定义 4.3】(杨氏半正规表示)

固定一个划分 \lambda\vdash n,记按末标号序递增排序的第 i 张标准杨表上的轴向距离为 d^i(a,b)。取置换 (t,t+1)\in S_n,定义其在 \rho_\lambda 上的杨氏半正规表示(Young's seminormal representation)为按以下规则构造的矩阵:

  1. 矩阵的维数即为 \lambda 所对杨表的个数,行和列的标号从 1 开始;
  2. 对于主对角线上的元素,有 (\rho_\lambda((t,t+1)))_{i,i}=d^i(t,t+1)^{-1}
  3. 对于末标号序第 i 的标准杨表,若它交换标号 tt+1 的位置后得到的杨表仍是标准的,设这张表的末标号序为 j,则 (\rho_\lambda((t,t+1)))_{i,j}=\left\{\begin{matrix}1-d^i(t,t+1)^{-2}&i<j\\1&i>j\end{matrix}\right..
  4. 其他未赋值的位置元素值为 0

现在我们得到了所有交换相邻元素的置换在 \rho_\lambda 上表示,那么容易根据表示的线性性写出所有 \pi\in S_n 的表示值。对于所有表示 \rho_\lambda((t,t+1))\;\{1\leq t\leq n-2\},由于去掉标号为 n 的格子后,其他标号的轴向距离不变,因此它们可以根据分支定理写成 (2.5) 分块矩阵的形式。为了保证末标号序递增,这些子矩阵应按其对应的划分 \lambda' 的字典序依次排列。至此,已经可以使用 (3.1) 式直接快速计算傅里叶变换了。

除了杨氏半正规表示,还有杨氏正交表示等其他常用基矢,也拥有各种性质,这里不做详细介绍。

五. 实现 & 复杂度分析

下面是对称群上的快速傅里叶变换的一个简单实现。我借鉴了资料 4(SnOB)的写法,删掉了一些不必要的类,并采用迭代法(资料 2 的算法 5)降低空间复杂度。采用编译器标准为 ISO C++11。

1. 类的定义

首先定义一个矩阵类,封装矩阵的基本运算。然后再定义对称群不可约表示类,这里采用了杨氏半正规表示,需要在接下来的预处理过程中完成计算。applyCycle() 用于左乘一个循环移位置换,使用 tdashdist 两个数组可以在不用构建矩阵的情况下算出贡献。

const int MaxN=11;

// 矩阵类
struct Matrix {
    int dim;
    double* A;

    // 生成一个 n*n 的方阵
    Matrix(int n): dim(n) { A = new double[n*n](); }

    // 复制矩阵 o
    Matrix(const Matrix& o) : dim(o.dim) {
        A = new double[dim*dim];
        for (int i=0; i<dim*dim; i++) A[i] = o.A[i];
    }

    // 构建 mlist 组成的分块矩阵
    Matrix(int n, const vector<Matrix*>& mlist): dim(n) {
        A = new double[n*n]();
        int t = 0;
        for (Matrix* mat: mlist) {
            for (int i=0; i<mat->dim; i++)
                for (int j=0; j<mat->dim; j++)
                    at(i+t, j+t) = mat->at(i, j);
            t += mat->dim;
        }
    }

    // 获取矩阵第 i 行第 j 列的元素
    double& at(int i, int j) const { return A[i*dim+j]; }

    // 矩阵乘法(O(n^3))
    Matrix* operator * (const Matrix& o) const {
        Matrix* res = new Matrix(dim);
        for (int i=0; i<dim; i++)
            for (int k=0; k<dim; k++)
                for (int j=0; j<dim; j++)
                    res->at(i, j) += at(i, k)*o.at(k, j);
        return res;
    }

    // 累加矩阵 o
    void operator += (const Matrix& o) {
        for (int i=0; i<dim*dim; i++) A[i] += o.A[i];
    }

    // 打印矩阵,调试用
    void print() {
        for (int i=0; i<dim; i++) {
            for (int j=0; j<dim; j++)
                printf("%lf ", at(i, j));
            putchar('\n');
        }
    }
};

// 对称群不可约表示类(Irreducible Representations)
struct Irrep {
    vector<int> part; // 对应的划分,part[i] 表示第 i 行的格子数
    int deg; // 表示的维数
    vector<int> eta; // 由分支定理拆分得到的 S_(n-1) 群表示编号
    int* tdash[MaxN]; // tdash[t][i] 为第 i 张杨表交换标号 t,t+1 得到的杨表的末标号序,得到的不是标准杨表则取 -1
    int* dist[MaxN]; // dist[t][i] 为第 i 张杨表上标号 t 到 t+1 的轴向距离

    Irrep(const vector<int>& p):
        part(p), deg(0) { eta.clear(); }

    // 将矩阵 M 左乘置换 [[p,q]] 上的表示矩阵,若 inv 为真则是左乘逆矩阵
    void applyCycle(Matrix& M, int p, int q, bool inv) const {
        bool done[deg];
        for (int l=q-1; l>=p; l--) {
            int t = inv ? p+q-1-l: l;
            for (int i=0; i<deg; i++) done[i]=0;
            for (int i=0; i<deg; i++) if (!done[i]) {
                int j = tdash[t][i];
                double c1 = 1.0/dist[t][i];
                if (j==-1) {
                    for (int k=0; k<deg; k++) M.at(i, k) *= c1;
                } else {
                    double c2 = 1-c1*c1;
                    for (int k=0; k<deg; k++) {
                        double tmp = M.at(i, k);
                        M.at(i, k) = c1*tmp+c2*M.at(j, k);
                        M.at(j, k) = -c1*M.at(j, k)+tmp;
                    }
                    done[j] = 1;
                }
            }
        }
    }
};

int order[MaxN]; // 群 S_n 的阶数(即 n!)
vector<Irrep> R[MaxN]; // S_n 的所有不可约表示
int* rev[MaxN]; // 迭代实现 FFT 需要预处理的数组,rev[i] 为字典序排名为 i 的排列倒置后的字典序

2. 预处理

预处理部分主要是完成对 Irrep 类以及 rev 数组的计算。这一部分的实现比较精细,使用了一些技巧来降低编码难度(比如不需要存储杨表),但代价是可读性稍差了一些。具体细节请结合注释理解。

void prework(int N) {
    // 直接写出 n=0,1 的情况
    R[0].push_back(Irrep(vector<int>()));
    R[0][0].deg = 1;

    R[1].push_back(Irrep(vector<int>({1})));
    R[1][0].deg = 1;
    R[1][0].eta.push_back(0);

    order[1] = 1;
    rev[1] = new int[1]();
    vector<int> lst({1}); // 这里的 lst[i] 为字典序第 i 的排列的最后一个元素,计算 rev 时要用到

    for (int n=2; n<=N; n++) {
        // 由 S_(n-1) 的表示推出 S_n 的表示,保证按划分的字典序递增排列
        for (const Irrep& lambda: R[n-1]) {
            vector<int> p = lambda.part;
            p.push_back(1);
            R[n].push_back(Irrep(p));
            p.pop_back();
            if (p.size()==1 || p[p.size()-1]<p[p.size()-2]) {
                p[p.size()-1]++;
                R[n].push_back(Irrep(p));
            }
        }

        int pos[R[n-2].size()];
        for (Irrep& lambda: R[n]) {
            vector<int> p = lambda.part;

            // 计算分支的编号 eta
            for (int i=0, j=0; i<p.size(); i++) {
                if (i==p.size()-1 || p[i]>p[i+1]) {
                    if (--p[i]==0) p.pop_back();
                    while (R[n-1][j].part!=p) j++;
                    lambda.deg += R[n-1][j].deg;
                    lambda.eta.push_back(j);
                    if (i==p.size()) p.push_back(0);
                    p[i]++;
                }
            }

            for (int i=1; i<n; i++) {
                lambda.tdash[i] = new int[lambda.deg];
                lambda.dist[i] = new int[lambda.deg];
                for (int j=0; j<lambda.deg; j++) lambda.tdash[i][j]=-1;
            }
            for (int i=0; i<R[n-2].size(); i++) pos[i]=-1;

            // 计算 tdash
            // 原理为依次删去杨图中标号为 n,n-1 的两个格子,若两种顺序不同的删法得到了同样的杨图,那么这两组杨表可以对应交换标号 n-1,n 互相得到
            // 第一次删格子得到某个杨图时在 pos 上做记录,第二次就可以直接找到这张杨图的编号了
            int t = 0;
            for (int i: lambda.eta) {
                for (int j=1; j<n-1; j++) {
                    for (int k=0; k<R[n-1][i].deg; k++) {
                        if (~R[n-1][i].tdash[j][k])
                            lambda.tdash[j][k+t] = R[n-1][i].tdash[j][k]+t;
                        lambda.dist[j][k+t] = R[n-1][i].dist[j][k];
                    }
                }
                for (int j: R[n-1][i].eta) {
                    if (~pos[j]) {
                        for (int k=0; k<R[n-2][j].deg; k++) {
                            lambda.tdash[n-1][t+k] = pos[j]+k;
                            lambda.tdash[n-1][pos[j]+k] = t+k;
                        }
                    }
                    pos[j] = t; // 不能加 else,否则计算 dist 时会出错
                    t += R[n-2][j].deg;
                }
            }

            // 计算 dist
            // 二次利用 pos 数组,pos[i] 此时记录了删去两个格子得到了 S_(n-2) 中编号 i 杨图的 n 阶杨图,且是末标号较大的一者
            // 根据这一关系可以得出两组杨表中标号 n-1,n 分别所在的位置,进一步得出 dist. 注意处理 n-1,n 在同一行的情况
            for (int i=0; i<R[n-2].size(); i++) {
                if (~pos[i]) {
                    int a=-1, b=-1;
                    for (int j=0; j<p.size(); j++)
                        if (j>=R[n-2][i].part.size() || p[j]>R[n-2][i].part[j])
                            if (a==-1) a=j; else b=j;
                    int d = ~b ? p[a]-p[b]+b-a : -1;
                    int tdash = lambda.tdash[n-1][pos[i]];
                    for (int j=0; j<R[n-2][i].deg; j++) {
                        lambda.dist[n-1][pos[i]+j] = d;
                        if (~tdash) lambda.dist[n-1][tdash+j] = -d;
                    }
                }
            }
        }

        order[n] = order[n-1]*n;
        // 由 S_(n-1) 的 lst,rev 推出 S_n 的
        rev[n] = new int[order[n]];
        lst.resize(order[n]);
        for (int i=0; i<order[n-1]; i++) lst[i]++;
        for (int i=2; i<=n; i++)
            for (int j=(i-1)*order[n-1]; j<i*order[n-1]; j++) {
                lst[j] = lst[j-order[n-1]];
                if (lst[j]==i) lst[j]--;
            }
        int q[MaxN]={0};
        for (int i=0; i<order[n]; i++)
            rev[n][i] = (lst[i]-1)*order[n-1]+rev[n-1][q[lst[i]]++];
    }
}

3. S_n 快速傅里叶变换

对数组 a 做傅里叶变换,返回的是一系列矩阵的指针。采用了迭代的思想,实现方法上文已有描述。

vector<Matrix*> FFT(double* a, int N) {
    vector<Matrix*> f(order[N]), g;
    for (int i=0; i<order[N]; i++) {
        f[i] = new Matrix(1);
        f[i]->at(0, 0) = a[rev[N][i]];
    }
    for (int n=2; n<=N; n++) {
        g.resize(order[N]/order[n]*R[n].size());
        for (int p=0; p<order[N]/order[n]; p++) {
            for (int i=0; i<R[n].size(); i++) {
                const Irrep& lambda = R[n][i];
                g[p*R[n].size()+i] = new Matrix(lambda.deg);
                for (int k=1; k<=n; k++) {
                    vector<Matrix*> branches;
                    for (int j: lambda.eta)
                        branches.push_back(f[(p*n+k-1)*R[n-1].size()+j]);
                    Matrix M(lambda.deg, branches);
                    lambda.applyCycle(M, k, n, 0);
                    (*g[p*R[n].size()+i]) += M;
                }
            }
        }
        swap(f, g);
        for (Matrix* mat: g) delete mat;
    }
    return f;
}

4. S_n 快速傅里叶逆变换

可以当作是傅里叶变换的逆过程,而不需要按 (2.2) 式来计算。这里的证明将作为一个重点,待补充。

double* iFFT(vector<Matrix*> f, int N) {
    vector<Matrix*> g;
    for (int i=0; i<R[N].size(); i++)
        f[i] = new Matrix(*f[i]);
    for (int n=N; n>=2; n--) {
        g.resize(order[N]/order[n-1]*R[n-1].size());
        for (int p=0; p<order[N]/order[n]; p++) {
            for (int k=1; k<=n; k++) {
                for (int j=0; j<R[n-1].size(); j++)
                    g[(p*n+k-1)*R[n-1].size()+j] = new Matrix(R[n-1][j].deg);
                for (int i=0; i<R[n].size(); i++) {
                    const Irrep& lambda = R[n][i];
                    Matrix M = *f[p*R[n].size()+i];
                    lambda.applyCycle(M, k, n, 1);
                    int c = 0;
                    for (int j: lambda.eta) {
                        Matrix* Msubset = g[(p*n+k-1)*R[n-1].size()+j];
                        int deg = R[n-1][j].deg;
                        double mult = (double)lambda.deg/(n*deg);
                        for (int a=0; a<deg; a++)
                            for (int b=0; b<deg; b++)
                                Msubset->at(a, b) += M.at(c+a, c+b)*mult;
                        c += deg;
                    }
                }
            }
        }
        swap(f, g);
        for (Matrix* mat: g) delete mat;
    }
    double* a = new double[order[N]];
    for (int i=0; i<order[N]; i++)
    {
        a[rev[N][i]] = f[i]->at(0, 0);
        delete f[i];
    }
    return a;
}

此外,模质数剩余系下的实现也很容易由上面的代码修改得到,不再赘述。

5. 复杂度分析

无法分析.jpg

取矩阵乘法的复杂度为 O(d^3),1990 年参考资料 2 提出一个较易证明的上界是 O(n(n!)^{1.5})。1993 年 Michael Clausen(资料 3)证明了 O(n^3n!) 的上界,这份实现也是基于他的算法的。1998 年 Maslen 对这个算法进行了改进,优化到了 O(n^2n!),但我还没看qwq

六. 参考资料

1 2 3 4 待补充。

未经允许不得转载:冷滟泽的个人博客 » 对称群上傅里叶变换的快速计算方法

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址