矩阵浅谈qwq
矩阵:
前置知识:
矩阵,定义太长了,其实就是个屑二维数组。(
他的运算也比较毒瘤。
- 加减法:
太屑了。
比如两个矩阵相加,其实就是对应位置加减去对应位置的数就行了。
- 乘除法:
乘法很毒瘤,但这就是定义,只有一个 \(a \times b\) 和一个 \(b \times c\) 的矩阵才能相乘,这里前后的顺序不能颠倒,因为这就是毒瘤定义,当且仅当一个矩阵的长等于另一个矩阵的宽才能乘。
乘完之后就乘了一个 \(a \times c\) 的矩阵。
然后我们现在只知道这样能乘,和乘完之后成为一个什么样的矩阵,那里有的值是啥呢。
我们设一个矩阵 \(A\) 为一个 \(a \times b\) 的矩阵,然后我们再设一个矩阵 \(B\) 为一个 \(b \times c\) 的矩阵,我们由前面告诉你的毒瘤定义可知, \(A \times B\) 后的矩阵 \(C\) 为一个 \(a \times c\) 的矩阵。
我们有以下公式可以求出矩阵中任意一个点的元素大小:
其实感觉有点像向量,虽然好像一点关系也没有。
- 数乘运算:
就是一个数乘一个 \(n \times m\) 矩阵,这个时候你可能就要问了啊,那你把一个数看成一个 \(1 \times 1\) 的矩阵,那这不就是不满足你的定义了吗。
这个时候我们跳出规则,把格局打开,再重新定义一手。
矩阵的数乘就是把这个数乘到每一个 \((i, j)\) 元上
- 矩阵转置:
就是把一个矩阵旋转 \(90^{o}\)。
这就是矩阵的一些基础前置知识,然后我们来讲矩阵快速幂。
- 单位矩阵
就相当于一个 \(1\), 主对角线上全为 \(1\),其余地方为 \(0\) 的矩阵就是单位矩阵。
矩阵快速幂
这可是个好东西。
比如你要得出了一个递推式,奈何他让你递推 \(10^{18}\),那你怎么办,那就用矩阵加速递推,那怎么加速递推呢,就用矩阵快速幂。
我们根据前面的定义知道一个 \(n \times m\) 矩阵 \(A\) 是可以不断的乘自己的,就是不断的转置,然后乘。
我们想要求出 \(A^k\)。
那该怎么求呢。
我们仿照快速幂的方法来求。
我们用一个结构体来存:
struct node {
int a[MAXN][MAXN];
node() {memset(a, false, sizeof a);}
void build() {for(int i = 1; i <= n; i++) a[i][i] = 1;} //构造单位矩阵
void print() {
for(int i = 1; i <=n;cout << '\n', i++){
for(int j = 1; j <= n; j++) {
cout << a[i][j] << " ";
}
}
}
}
应该没有人看不懂吧。
然后乘法就是仿照之前提到的式子模拟即可。
node operator * (const node &x, const node &y) {
node z;
for(int l = 1; l <= n; l++) {
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
z.a[i][j] = (z.a[i][j] + x.a[i][l] * y.a[l][j] % mod) % mod;
}
}
}
return z;
}
\(z\) 就是我们 \(x \times y\) 的矩阵。
然后具体求解的时候也就是 \(A^k\)。
,
这里 \(ans\) 是 \(A^k\),\(b\) 就是 \(A\)。
node ksm(node A, int k) {
node res;
res.build();
while(k) {
if(k & 1) res = res * A;
A = A * A, k >>= 1;
}
return res;
}
然后我们就愉快的将模板题切掉了。
矩阵加速递推:
那什么样的递推式才能用矩阵加速递推呢,就是给你前几项,然后后面的等于前面某几项的和,要是按照递推式推过去会超时,这个时候就需要用到矩阵来加速递推了。
具体是怎么递推呢,我们需要构造两个矩阵,一个是初始矩阵,一个是递推矩阵,这个递推矩阵一般是一个 \(3 \times 3\) 的矩阵。
比如我们的通项公式为 \(a_i = a_{i - 1} + a_{i - 3}\),然后我们知道 \(a_1, a_2, a_3\),如果我让你求 \(a_{1e8}\)。
我们就用初始矩阵去乘上递推矩阵的 \(10^8\) 就能得到了。
那怎么去构造这两个矩阵呢。
画个图来感性理解一下 qwq
。
\(\begin{vmatrix}a_{i - 1}\\a_{i - 2}\\a_{i - 3}\end{vmatrix}\) \(\times\) \(\begin{vmatrix}z&&h&&b\\x&&j&&p\\s &&s&&h\end{vmatrix}\) \(=\) \(\begin{vmatrix}a_{i}\\a_{i - 1}\\a_{i - 2}\end{vmatrix}\)
我们要求出第二个矩阵,观察一下这两个矩阵 \(a_{i - 1}\) 和 \(a_i\) 相对,然后根据通项公式可知,\(a_{i} = a_{i - 1}\ +\ a_{i - 3}\),所以 \(z\ =\ 1\ \&\ x\ =\ 0\ \&\ s\ =\ 1\)。剩下的一个样。
最后我们求出来第二个矩阵 :
\(\begin{vmatrix}1&&1&&0\\0&&0&&1\\1 &&0&&0\end{vmatrix}\)
然后开始乘就行了,其实挺简单的。
代码贴贴:
struct node {
int a[MAX][MAX];
node() {memset(a, false, sizeof a); }
void build() { for(int i = 1; i <= 3; i++) a[i][i] = 1; }
}Frist, t;
node operator * (const node &x, const node &y) {
node res;
for(int i = 1; i <= 3; i++) {
for(int k = 1; k <= 3; k++) {
for(int j = 1; j <= 3; j++) {
res.a[i][j] = (res.a[i][j] + x.a[i][k] * y.a[k][j] % mod) %mod;
}
}
}return res;
}
node Ksm(node x, int k) {
node res;
res.build();
while(k) {
if(k & 1) res = res * x;
x = x * x, k >>= 1;
}return res;
}
Frist.a[1][1] = Frist.a[1][2] = Frist.a[1][3] = 1;
t.a[1][1] = t.a[1][2] = t.a[2][3] = t.a[3][1] = 1;