转置原理学习笔记

本文参考 wangrx 浅谈转置原理 和 Vocalise 的博客。

1.矩阵的初等变换

也是高斯消元的基础。

1.1 定义

对矩阵施以下三种变换,称为矩阵的初等变换 :

  • 交换矩阵的两行(列)
  • 以一个非零数 k 乘矩阵的某一行(列)
  • 把矩阵的某一行(列)的 l 倍加于另一行(列)

对单位矩阵 I 施以一次初等变换得到的矩阵,称为初等矩阵。

1.2 一些定理

Am×n=(aij)m×n

  • 定理 1 :对 A 的行施以一次初等变换,等同于用同种 m 阶初等矩阵左乘 A。对 A 的列施以一次初等变换,等同于用同种 n 阶初等矩阵右乘 A

    Proof:将 AI 矩阵分块即可。

容易验证,初等矩阵都是可逆的,且它们的逆仍是初等矩阵。

I(ij)1=I(ij),I(i(k))1=I(i(1k)),I(ij(l))1=I(ij(l))

  • 定理 2 :n 阶矩阵 A 可逆的充要条件是它可以写成一些初等矩阵的乘积。

    因此,A 可以表示成若干个初等矩阵的乘积。

2.算法的转置

2.1 定义

将一个算法看做方阵 A,输入向量为 v,输出向量为 Av,则称该算法为线性算法。

许多算法都是线性算法,例如 FFT 的单位根矩阵。

2.2 转置的定义和性质

当输入为 v,输出为 ATv 的算法称为该算法的转置算法。

转置的几个性质:

  • (AB)T=BTAT

  • 对于初等矩阵的转置: 前两种的转置是它本身。最后一种如果是将第 i 行(列)的 k 倍加到第 j 行,

    则其转置为将第 j 行(列)的 k 倍加到第 i 行(列)。

2.3 线性算法的判定

但算法的实际流程并未与矩阵建立起对应的方式。我们需要找到一种途径来刻画这个过程。

将输入,输出变量和运行过程中的一切辅助变量(也就是整个内存)拼成一个向量。

也就是已知 [v00], 求 [00Av]=[O   O   OO   O   OA   O   O][v00].

A 不是方阵,也可以通过补 0 补成方阵。

称向量中的量为变量,矩阵中的量为常量。

具体的,线性算法只包含一下运算:

  • 交换两个变量。
  • xm×xx 为变量, m 为常量。
  • xx+m×yx,y 为变量,m 为常量。

2.4 原算法与转置算法的转换

将矩阵分解为初等矩阵,设 A=E1E2En,则有 AT=EnTEn1TE1T

由转置的性质可知,将算法转置相当于将运算顺序反过来,再讲 xx+my,改写成 yy+mx 即可。

3.常见算法的转置

推荐阅读:127: 浅谈转置原理

3.1 前缀和

前缀和问题 :bi=jiai,转置为后缀和问题 ai=ijbj

for(int i = 1; i <= n; ++i) a[i] += a[i - 1];

首先将执行顺序倒序,变成 :

for(int i = n; i >= 1; --i) \\ do something..

然后将 aiai+ai1 转置为 ai1ai1+ai

for(int i = n; i >= 1; --i) a[i - 1] += a[i]; 

3.2 DFT

n 阶 DFT 形如 :Fi,j=(ωni)j ,其转置为本身,即 FT=FFi,j1=2n(ωnj)i

原算法的核心步骤是 : [1   wj1 wj][aj+kaj+k+i],转置为 : [1    1wj wj][aj+kaj+k+i]

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 多项式乘法

原算法:已知 n+1 维向量 am+1 维向量 ba 是变量, b 是常量。求 n+m+1 维向量 c,使得:

ck=iaibki

ckck+aibki,转置为 : aiai+ckbki

即已知 n+m+1 维向量 cm 维向量 b,求 c×bR 的 第 mm+n 个元素。

是一个差卷积的形式,将 b 翻转后卷积即可。

由转置的过程本身可知, 原算法与转置算法的复杂度相同。

若得到计算 Av 的优化方法,则必然可以通过如上机械地改写得到 ATv 的优化算法。

这便是转置原理,又称特勒根原理。

3.4 多项式多点求值

问题:给定 n 次多项式 f(x),对于每个 i[1,m],求出 f(ai)

f(ai)=i=0n1fiaii

fi 看作输入向量,则 C 即为 :

[1   a0   a02      a0n11   a1   a12      a1n1                         1   am   am2      amn1][f0f1fn]

将矩阵补成方阵,转置得到:

[1   1     1a0   a1    ana02   a12    an2               a0n   a1n      ann]

即 $g_i = \sum\limits_{j=0}{n}f_ja_ji $,写成生成函数可以得到 : G(x)=i=0nfi1aix

这个 G(x) 可以通过分治记录下分子 fL,R 和分母 gL,R 得到:

gL,R=gL,mid×gmid+1,R

fL,R=fL,mid×gmid+1,R+fmid+1,R×gL,mid

转化为线性算法的标准形式:

  • 自下而上初始化常量 xigL,L=[1xi]gL,R=gL,mid×gmid+1,R

  • 初始化向量为 [f0]

  • 赋值 fL,LfL

  • 自下而上分治 : fL,midfL,mid×gmid+1,Rfmid+1,Rfmid+1,R×gL,mid

    fL,RfL,R+fL,midfL,RfL,R+fmid+1,R

  • 最后计算 ff1,n×g1,n1

  • 将辅助变量清零。

转置算法:

  • 初始化常量 gL,R

  • 初始化向量为 [f0]

  • 将辅助变量清零。

  • 计算 f1,nf×Tg1,n1×T 为多项式乘法的转置。

  • 自上而下分治:

    fmid+1,Rfmid+1,R+fL,R

    fL,midfL,mid+fL,R

    fmid+1,Rfmid+1,R×gL,mid

    fL,midfL,mid×gmid+1,R

  • 赋值 fifL,L

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 中国大陆许可协议进行许可。

posted @   henrici3106  阅读(619)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.