转置原理

· · 算法·理论

不会写正经的伪代码,所以可能有点鬼畜。

概述

转置原理是给出通过问题 b=Aa 的算法解决问题 b'=A^{T}a' 的一种方法。

具体来说,我们先将 A 分解为若干容易翻译(即容易得到转置操作)的操作,即 A=A_{m}A_{m-1} \cdots A_{1}

根据矩阵转置的相关信息,我们可以得到 A^T=A_{1}^{T} \cdots A_{m-1}^{T}A_{m}^{T}

也即,我们只要把原来问题(b=Aa)的算法倒过来,执行转置操作,就可以解决新的问题(b'=A^{T}a')。

常见操作的转置操作

无特殊说明,以下内容 ab 为变量,c 为常量。

将这个操作翻译为矩阵形式,即:

\begin{bmatrix}1 & 0 \\ c & 1\end{bmatrix}\begin{bmatrix}a \\ b\end{bmatrix}=\begin{bmatrix}a \\ ac+b\end{bmatrix}

于是其转置操作即为:

\begin{bmatrix}1 & c \\ 0 & 1\end{bmatrix}\begin{bmatrix}a \\ b\end{bmatrix}=\begin{bmatrix}a+bc \\ b\end{bmatrix}

a \leftarrow a+cb

这个变换是一个 1 \times 1 的矩阵,转置后不变。

与 $a \leftarrow ca$ 不同的是,它是一段内存整体的操作,所以不能直接套用以上的操作。 可以先将三个多项式的大小统一一下。记 $n$ 为次数。 然后仍然考虑写为矩阵形式,我们可以对 $B$ 构造一个矩阵 $M$ 使得 $C=MA$。可以得出: $$ M=\begin{bmatrix} B_0 & 0 & 0 & \cdots \\ B_1 & B_0 & 0 & \cdots\\ B_2 & B_1 & B_0 & \cdots\\ \vdots & \vdots & \vdots & \ddots \end{bmatrix} $$ 我们去考虑他的转置,即: $$ M=\begin{bmatrix} B_0 & B_1 & B_2 & \cdots \\ 0 & B_0 & B_1 & \cdots\\ 0 & 0 & B_0 & \cdots\\ \vdots & \vdots & \vdots & \ddots \end{bmatrix} $$ 那么:$C_k=\sum_{i=0}^{n}B_{i-k}A_i$。 考虑记 $E_i=B_{n-i}$,则 $C_k=\sum_{i=0}^{n}E_{n+k-i}A_i=\sum_{i=0}^{n+k}E_{n+k-i}A_i$。 也就是,我们将 $B$ 反转再与 $A$ 做乘法,最后左移 $n$ 位,截取即可。 参考实现: ```cpp poly mulT(poly f,poly h){ int n=(int)f.size(); f.resize(n),h.resize(n);reverse(h.begin(),h.end());h=mul(f,h); for(int i=0;i<n;i++)f[i]=h[i+(n-1)]; return f; } ``` ps:为了方便,此后多项式乘法的转置记为 $\times^{T}$。 upd:事实上,我们可以从 DFT 的角度考虑多项式乘法。我们知道 IDFT 可以拆成一次 DFT、一次数乘以及一次逆序。所以多项式乘法就是 DFT、点乘、DFT、数乘、逆序。 我们知道 DFT 转置后不变,逆序是副对角线,转置后不变,点乘和数乘都是主对角线也不变,所以转置算法就是 逆序、数乘、DFT、点乘、DFT。 ### 应用 #### 多项式多点求值 [题目](https://www.luogu.com.cn/problem/P5050) 假设给出的多项式为 $\sum_{i=0}^{n} a_ix^{i}$,需要求得点值为 $\{x_i|0 \leq i \leq n\}$。 设矩阵 $M$ 有 $M_{ij}=x_i^j$,向量 $A$ 有 $A_i=a_i$,问题即为: $$ \begin{bmatrix}1 & 0 \\ M & 1\end{bmatrix} \begin{bmatrix}A \\ D\end{bmatrix} =\begin{bmatrix}A \\ D+MA\end{bmatrix} $$ 考虑其转置问题: $$ \begin{bmatrix}1 & M^{T} \\ 0 & 1\end{bmatrix} \begin{bmatrix}A \\ D\end{bmatrix} =\begin{bmatrix}A+M^{T}D \\ D\end{bmatrix} $$ 于是我们要考虑的就是 $A=M^{T}D$ 了,用人话说就是 $A_k=\sum_{i=0}^{n}M^{T}_{k,i}D_i=\sum_{i=0}^{n}M_{i,k}D_i=\sum_{i=0}^{n}D_ix_{i}^{k}$。 挂在 GF 上,令 $Q=\sum_{k \geq 0}A_kt^k=\sum_{k \geq 0}\sum_{i=0}^{n}D_i(x_{i}t)^{k}=\sum_{i=0}^{n}\frac{D_i}{1-x_{i}t}$,可以分治乘解决。 于是,我们解决了原问题的转置问题,可以写出其伪代码(注意,常量的计算可以无需体现在算法中)。 以下 $Q_{l,r}=\prod_{i=l}^{r}(1-x_it)$。 ``` sol(l,r): if(l=r): f[0]=f[0]+D[l] return f mid=(l+r)/2 L=sol(l,mid) R=sol(mid+1,r) f=f+L*Q[mid+1][r] f=f+R*Q[l][mid] A=A+sol(0,n)*inv(Q[0][n]) ``` 将执行顺序反转,执行转置操作,于是我们可以解决原问题。 ``` sol(l,r,f): if(l=r): D[l]=D[l]+f[0] mid=(l+r)/2 sol(l,mid,f (*T) Q[mid+1][r]) sol(mid+1,r,f (*T) Q[l][mid]) sol(0,n,A (*T) inv(Q[0][n])) ``` 参考实现: ```cpp poly Q[N<<2|1]; void init(poly& X,int x,int l,int r){ if(l==r){ Q[x].clear();Q[x].push_back(1);Q[x].push_back((mod-X[l])%mod); return; } int mid=(l+r)>>1; init(X,x<<1,l,mid);init(X,x<<1|1,mid+1,r); Q[x]=mul(Q[x<<1],Q[x<<1|1]); return; } void sol(poly F,poly &a,int x,int l,int r){ F.resize(r-l+1); if(l==r)return void(a[l]=F[0]); int mid=(l+r)>>1; sol(mulT(F,Q[x<<1|1]),a,x<<1,l,mid); sol(mulT(F,Q[x<<1]),a,x<<1|1,mid+1,r); } void multipoint(poly A,poly X,poly &v,int n){ A.resize(n);X.resize(n);v.resize(n); init(X,1,0,n-1); sol(mulT(A,inv(Q[1],n)),v,1,0,n-1); } ``` #### 特殊的 XOR FWT [是这题](https://atcoder.jp/contests/arc133/tasks/arc133_f) 转化一下问题:$N$ 枚硬币,初始时 $i$ 个正面的概率为 $p_i$,每次随机翻一枚,求最后正面朝上硬币个数为 $x$ 的概率。 我们显式的表达这个序列,假设硬币序列的状态为 $S$,其出现概率为 $P_S$。 每一次操作取反一位,可以转化为 XOR 卷积。 如果只是这样的话时间复杂度只能做到 $O(n2^n)$。 但是这题有性质:若 $|i|=|j|$ 则 $P_i=P_j$($|S|=\operatorname{popcount}(S)$)。 所以记 $g_{|s|}=FWT(P)_s$、$f_{|s|}=P_s$,有: $$ FWT(P)_s = \sum_{t}(-1)^{|s \cap t|}P_t\\ g_{|s|} = \sum_{t}(-1)^{|s \cap t|}f_{|t|}\\ g_{k}=\sum_{i=1}^{n}f_i[x^i](1-x)^{k}(1+x)^{n-k} $$ 对于这个问题,转置一下即得: $$ f_k=\sum_{i=1}^{n}g_i[x^k](1-x)^{i}(1+x)^{n-i} $$ 这是容易分治乘解决的,时间复杂度 $O(n\log^{2}{n})$。 于是我们可以直接用 FWT,然后乘起来就好了。 [参考实现](https://www.luogu.com.cn/paste/g3lc7ir8) ps:刚学完转置原理就随机跳题跳到了,令人忍俊不禁。然鹅看题解发现其实不需要转置。