转置原理学习笔记
本文参考 wangrx 浅谈转置原理 和 Vocalise 的博客。
1.矩阵的初等变换
也是高斯消元的基础。
1.1 定义
对矩阵施以下三种变换,称为矩阵的初等变换 :
- 交换矩阵的两行(列)
- 以一个非零数 乘矩阵的某一行(列)
- 把矩阵的某一行(列)的 倍加于另一行(列)
对单位矩阵 施以一次初等变换得到的矩阵,称为初等矩阵。
1.2 一些定理
设
-
定理 1 :对 的行施以一次初等变换,等同于用同种 阶初等矩阵左乘 。对 的列施以一次初等变换,等同于用同种 阶初等矩阵右乘 。
Proof:将 和 矩阵分块即可。
容易验证,初等矩阵都是可逆的,且它们的逆仍是初等矩阵。
-
定理 2 : 阶矩阵 可逆的充要条件是它可以写成一些初等矩阵的乘积。
因此, 可以表示成若干个初等矩阵的乘积。
2.算法的转置
2.1 定义
将一个算法看做方阵 ,输入向量为 ,输出向量为 ,则称该算法为线性算法。
许多算法都是线性算法,例如 FFT 的单位根矩阵。
2.2 转置的定义和性质
当输入为 ,输出为 的算法称为该算法的转置算法。
转置的几个性质:
-
-
对于初等矩阵的转置: 前两种的转置是它本身。最后一种如果是将第 行(列)的 倍加到第 行,
则其转置为将第 行(列)的 倍加到第 行(列)。
2.3 线性算法的判定
但算法的实际流程并未与矩阵建立起对应的方式。我们需要找到一种途径来刻画这个过程。
将输入,输出变量和运行过程中的一切辅助变量(也就是整个内存)拼成一个向量。
也就是已知 , 求 .
若 不是方阵,也可以通过补 补成方阵。
称向量中的量为变量,矩阵中的量为常量。
具体的,线性算法只包含一下运算:
- 交换两个变量。
- , 为变量, 为常量。
- 为变量, 为常量。
2.4 原算法与转置算法的转换
将矩阵分解为初等矩阵,设 ,则有 。
由转置的性质可知,将算法转置相当于将运算顺序反过来,再讲 ,改写成 即可。
3.常见算法的转置
推荐阅读:127: 浅谈转置原理
3.1 前缀和
前缀和问题 :,转置为后缀和问题 。
for(int i = 1; i <= n; ++i) a[i] += a[i - 1];
首先将执行顺序倒序,变成 :
for(int i = n; i >= 1; --i) \\ do something..
然后将 转置为 。
for(int i = n; i >= 1; --i) a[i - 1] += a[i];
3.2 DFT
阶 DFT 形如 : ,其转置为本身,即 ,。
原算法的核心步骤是 : ,转置为 :
inline void DFT(int *a, int n) {
for(int i = n >> 1; i; i >>= 1) {
int x = qpow(3, (mod - 1) / (i << 1), mod);
for(int j = 0; j < n; j += (i << 1)) {
int y = 1;
for(int k = 0; k < i; ++k) {
int p = a[j + k], q = a[j + k + i];
a[j + k] = (p + q) % mod;
a[j + k + i] = 1ll * (p - q + mod) * y % mod;
}
}
}
for(int i = 0; i < n; ++i) if(i < rev[i]) std :: swap(a[i], a[rev[i]]);
}
考虑在 IDFT 时使用原算法,这样我们就免去了两次位逆序的过程。
3.3 多项式乘法
原算法:已知 维向量 , 维向量 , 是变量, 是常量。求 维向量 ,使得:
即 ,转置为 : 。
即已知 维向量 , 维向量 ,求 的 第 到 个元素。
是一个差卷积的形式,将 翻转后卷积即可。
由转置的过程本身可知, 原算法与转置算法的复杂度相同。
若得到计算 的优化方法,则必然可以通过如上机械地改写得到 的优化算法。
这便是转置原理,又称特勒根原理。
3.4 多项式多点求值
问题:给定 次多项式 ,对于每个 ,求出 。
即 。
将 看作输入向量,则 即为 :
将矩阵补成方阵,转置得到:
即 $g_i = \sum\limits_{j=0}{n}f_ja_ji $,写成生成函数可以得到 :
这个 可以通过分治记录下分子 和分母 得到:
转化为线性算法的标准形式:
-
自下而上初始化常量 。,。
-
初始化向量为 。
-
赋值 。
-
自下而上分治 : ,。
,。
-
最后计算
-
将辅助变量清零。
转置算法:
-
初始化常量 。
-
初始化向量为 。
-
将辅助变量清零。
-
计算 , 为多项式乘法的转置。
-
自上而下分治:
-
赋值 。
P5050 【模板】多项式多点求值 代码如下:
#include <bits/stdc++.h>
const int M = 3e5 + 5;
const int INF = 0x3f3f3f3f;
const int mod = 998244353, G = 3, InvG = (mod + 1) / G;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
typedef std :: vector < int > Poly;
int n, m;
int rev[M];
inline int qpow(int a, int b, int p) {
int s = 1;
for(int bas = a; b; b >>= 1, bas = 1ll * bas * bas % p)
if(b & 1) s = 1ll * s * bas % p;
return s;
}
inline int add(int x, int y) {return x + y >= mod ? x + y - mod : x + y;}
inline int dec(int x, int y) {return x < y ? x - y + mod : x - y;}
inline void DFT(Poly &a) {
const int n = a.size();
for(int i = 0; i < n; ++i) rev[i] = (rev[i >> 1] >> 1) | (i & 1 ? n >> 1 : 0);
for(int i = 0; i < n; ++i) if(i < rev[i]) std :: swap(a[i], a[rev[i]]);
for(int i = 1; i < n; i <<= 1) {
int x = qpow(G, (mod - 1) / (i << 1), mod);
for(int j = 0; j < n; j += (i << 1)) {
int y = 1;
for(int k = 0; k < i; ++k, y = 1ll * y * x % mod) {
int p = a[j + k], q = 1ll * y * a[j + k + i] % mod;
a[j + k] = add(p, q), a[j + k + i] = dec(p, q);
}
}
}
}
inline void IDFT(Poly &a) {
const int n = a.size();
for(int i = 0; i < n; ++i) rev[i] = (rev[i >> 1] >> 1) | (i & 1 ? n >> 1 : 0);
for(int i = 0; i < n; ++i) if(i < rev[i]) std :: swap(a[i], a[rev[i]]);
for(int i = 1; i < n; i <<= 1) {
int x = qpow(InvG, (mod - 1) / (i << 1), mod);
for(int j = 0; j < n; j += (i << 1)) {
int y = 1;
for(int k = 0; k < i; ++k, y = 1ll * y * x % mod) {
int p = a[j + k], q = 1ll * y * a[j + k + i] % mod;
a[j + k] = add(p, q), a[j + k + i] = dec(p, q);
}
}
}
int inv = qpow(n, mod - 2, mod);
for(int i = 0; i < n; ++i) a[i] = 1ll * a[i] * inv % mod;
}
inline Poly Mul(Poly a, Poly b) {
const int n = a.size() + b.size() - 1; int lim;
for(lim = 1; lim < n; lim <<= 1);
a.resize(lim), b.resize(lim), DFT(a), DFT(b);
for(int i = 0; i < lim; ++i) a[i] = 1ll * a[i] * b[i] % mod;
IDFT(a); a.resize(n);
return a;
}
inline Poly MulT(Poly a, Poly b) {
const int n = a.size(), m = b.size();
std :: reverse(b.begin(), b.end());
b = Mul(a, b);
for(int i = 0; i < n; ++i) a[i] = b[i + m - 1];
return a;
}
Poly tmp;
inline Poly Inv(Poly a, int n) {
if(n == 1) return Poly(1, qpow(a[0], mod - 2, mod));
Poly b = Inv(a, n + 1 >> 1);
int lim; for(lim = 1; lim <= 2 * n; lim <<= 1);
tmp.resize(lim), b.resize(lim); for(int i = 0; i < n; ++i) tmp[i] = a[i];
for(int i = n; i < lim; ++i) tmp[i] = 0;
DFT(tmp), DFT(b);
for(int i = 0; i < lim; ++i) b[i] = 1ll * b[i] * dec(2, 1ll * b[i] * tmp[i] % mod) % mod;
IDFT(b), b.resize(n); return b;
}
Poly P[M], f, a, v;
inline void build_G(int p, int l, int r, Poly &a) {
if(l == r) {
P[p].push_back(1);
if(a[l]) P[p].push_back(mod - a[l]);
else P[p].push_back(0);
return ;
}
int mid = l + r >> 1;
build_G(p << 1, l, mid, a), build_G(p << 1 | 1, mid + 1, r, a);
P[p] = Mul(P[p << 1], P[p << 1 | 1]);
}
inline void build_F(int p, int l, int r, Poly F, Poly &v) {
F.resize(r - l + 1);
if(l == r) return v[l] = F[0], void();
int mid = l + r >> 1;
build_F(p << 1, l, mid, MulT(F, P[p << 1 | 1]), v);
build_F(p << 1 | 1, mid + 1, r, MulT(F, P[p << 1]), v);
}
inline void MultiPoint(Poly f, Poly a, Poly &v, int n) {
f.resize(n + 1), a.resize(n);
build_G(1, 0, n - 1, a), v.resize(n), build_F(1, 0, n - 1, MulT(f, Inv(P[1], n + 1)), v);
}
inline int read() {
int f = 1, s = 0; char ch = getchar();
while(!isdigit(ch)) (ch == '-') && (f = -1), ch = getchar();
while(isdigit(ch)) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar();
return f * s;
}
int main()
{
n = read() + 1, m = read();
for(int i = 0, x; i < n; ++i) x = read(), f.push_back(x);
for(int i = 0, x; i < m; ++i) x = read(), a.push_back(x);
MultiPoint(f, a, v, std :: max(n, m));
for(int i = 0; i < m; ++i) printf("%d\n", v[i]);
return 0;
}
3.5 More...
本文作者:henrici3106
本文链接:https://www.cnblogs.com/henrici3106/p/16069705.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步