酷炫转置原理
这真的很酷炫。
感谢队爷 Cap1tal 为我讲解如何使用转置原理解决省集 day2t3,虽然几乎没听懂。
对于 \(m\times n\) 的矩阵 \(A\),任意输入 \(n\times 1\) 向量 \(a\) 均可被计算得到输出向量 \(b=Aa\) 的一个算法,称作针对矩阵 \(A\) 的线性变换算法 \(f\)。
举个例子,对于后缀和算法,其就是针对如下上三角矩阵的线性变换算法:
对于任意输入规模为 \(n\) 的向量 \(a\),我们的输出向量总为 \(b=Aa\)。
对于 \(n\times m\) 的矩阵 \(A^T\)(代表 \(A\) 的转置),任意输入 \(m\times 1\) 向量 \(a'\) 均可被计算得到输出向量 \(b'=A^Ta'\) 的一个算法,将针对矩阵 \(A^T\) 的线性变换算法 \(g\) 称为算法 \(f\) 的转置。
后缀和算法的转置算法为前缀和算法,为一个下三角矩阵。
转置原理指出,可以将线性变换算法改写其转置算法,同时保持时空复杂度不变,该定理利用下面的线性代数语言改写更为直观。
对 \(A\) 添加若干列和若干行使其成为可逆方阵便于处理,以下均默认矩阵 \(A\) 可逆,同时令 \(E_n\) 代表 \(n\) 阶单位矩阵,\(O\) 代表全零矩阵,构造:
此时输入向量对应增加 \(m\) 行,将其设置为 \(0\) 不会影响输出向量前 \(m\) 行的值,同时输出向量的后 \(n\) 行就是输入向量的前 \(n\) 行。由于构造后的 \(A\) 为可逆方阵,可以将其拆分成若干初等矩阵的乘积:\(A=V_1V_2\dots V_k\),线性代数上有 \(A^T=V_K^T\dots V_2^TV_1^T\)。因此我们可以利用分解结果构造出其的转置算法。
具体地,考虑初等行变换:
-
\(P_1(i,j)\),交换 \(ij\) 两行。
-
\(P_2(i,c)\),将第 \(i\) 行变成 \(c\) 倍。
-
\(P_3(i,j,c)\),将第 \(i\) 行的 \(c\) 倍加到第 \(j\) 行上。
三种初等矩阵分别形如交换单位矩阵两行、将单位矩阵对角线上某一点变为 \(c\),将单位矩阵非对角线上某一点变为 \(c\)。不难发现前两种转置后矩阵不变,第三种转置后变为 \(P_3(j,i,c)\)。将 \(A\) 分解后倒序执行转置即可。
以上就是转置原理的全部内容,至于其实际应用,我们暂且略过在多项式和生成函数中的应用,着重研究其在线段树上的应用。
考虑一个简单的问题,在一个初始全为 \(0\) 的长为 \(n\) 的序列 \(a\) 上支持区间加和单点查询两种操作,操作参数为:
-
区间加:\(l_i,r_i,x_i\)。
-
单点查询:输入 \(q_i\),输出 \(k_i\)。
我们考虑将所有的 \(l_i\)、\(q_i\) 和 \(r_i\) 看作参数扔到矩阵 \(A\) 中,将 \(x_i\) 看作输入向量。对于每个询问都可以写作 \(y_i=\sum_{j\in S_i}x_j\),其中 \(S_i\) 是被时间 \(i\) 和位置 \(q_i\) 确定的一个集合。此时可以发现解决该问题的算法是线性的。
令 \(n_1\) 为区间加操作的数量,\(n_2\) 为单点查询的数量。
考虑暴力算法,将输入向量和输出向量都扩张成 \((n_1+n_2+n)\) 维向量,将 \(A\) 扩张为 \((n_1+n_2+n)\) 阶方阵。输入向量的后 \(n\) 维存储序列 \(a\),前 \(n_1\) 维为所有 \(x_i\),中间 \(n_2\) 维为答案 \(k_i\)。在暴力算法中:
-
区间加操作 \(l_i,r_i,x_i\),执行 \(P_3(l_i,x_i,1)P_3(l_{i+1},x_i,1)\dots P_3(r_i,x_i,1)\)。
-
单点查询操作 \(q_i,k_i\),执行 \(P_3(k_i,q_i,1)\)。
转置结果:
-
区间加操作 \(l_i,r_i,x_i\),执行 \(P_3(x_i,l_i,1)P_3(x_i,l_{i+1},1)\dots P_3(x_i,r_i,1)\),由于区间加操作的矩阵顺序是无所谓的,此处不加区分。
-
单点查询操作 \(q_i,k_i\),执行 \(P_3(k_i,q_i,1)\)。
构造一个新问题来符合这个转置结果。不难发现如下问题:
-
单点加操作 \(k_i,q_i\)。
-
区间查询操作 \(l_i,r_i,x_i\)。
其中输出为 \(x_i\)。因此在暴力操作下,单点加、区间查询操作是区间加、单点查询操作的转置算法。
以下采用标记永久化。在线段树做法中,我们将输入向量扩张成 \(n_1+2n-1+n_2\) 维向量,矩阵 \(A\) 也对应扩张,其中 \(2n-1\) 是序列的线段树节点。有:
-
区间加操作 \(l_i,r_i,x_i\),设将 \([l_i,r_i]\) 拆分成线段树上的若干节点 \(S_{pos}\)。执行操作 \(\prod\limits_{s\in S_{pos}}P_3(s,x_i,1)\)。
-
单点查询操作 \(q_i,k_i\),设所有包含 \(q_i\) 的节点为 \(S_{pos}\)。执行操作 \(\prod\limits_{s\in S_{pos}}P_3(k_i,s,1)\)。
转置结果:
-
区间加操作 \(l_i,r_i,x_i\),执行操作 \(\prod\limits_{s\in S_{pos}}P_3(x_i,s,1)\)。
-
单点查询操作 \(q_i,k_i\),执行操作 \(\prod\limits_{s\in S_{pos}}P_3(s,k_i,1)\)。
不难发现同样等价于单点加、区间查询操作。
由上例,可以发现转置原理在数据结构上的应用有两种方式。对暴力算法转置后使用线段树优化,线段树优化暴力算法后转置线段树算法。转置原理并没有保证前者的复杂度一定正确,但是保证了后者的复杂度一定正确(前提是线段树优化暴力算法的时间复杂度正确)。
NOI2024 D2T2 登山
设答案向量为 \(F=[f_2,f_3,\dots,f_n]^T\),直接正向思考转置原理并不好做。考虑若有问题:
输入为一组系数向量 \(b'=[v_1,v_2,\dots,v_n]^T\),定义一个路径的权值为起点的权值,求权值和。
该问题可以写作 \(a'=F^Tb'\)。根据转置原理,问题 \(a=Fb\) 可以在同样的时空复杂度内解决,取 \(b=[1]\) 即可求出原问题的答案。这样好像很没有说服力,因为 \(F\) 是被原问题输入确定的,一个问题不可能存在两个输入。考虑我们将原问题视为一个先决的、已经存在的环境,所有原问题的输入都是自然存在、非人为输入的参数,若在这个环境下我们总能在 \(\tau\) 的时间内求出新问题的答案,则在新环境下求出 \(a=Fb\) 的时间复杂度也是 \(\tau\)。一个常见的问题是我们无法直接从先天环境中获得 \(F\),但是 \(F\) 并非不存在,我们解决的新问题表述为 \(b'=F^Ta'\) 是没有问题的,进而,转置原理在该种条件下成立。
注意到一个点不能在一个路径中同时被冲刺和休息,每次冲刺到的点呈现祖先关系,每次冲刺后当前的限制都会被冲刺到的点更新。
\(g_x\) 代表所有冲刺到 \(x\) 的路径权值之和,\(c_{x,j}\) 代表将 \(x\) 作为起点,第一步冲刺是到达深度为 \(j\) 的节点的路径数量,\(t_{x,j}\) 代表从 \(x\) 的子树中任选一个起点,冲刺到深度为 \(j\) 的祖先的权值之和。分别考虑转移: