题目描述
给定 n×n 矩阵 A 和正整数 k,求和 S=A+A2+A3+⋯+Ak。
输入格式
输入只包含一个测试用例。
第一行输入包含三个正整数 n,k 和 m。
接下来 n 行,每行包含 n 个非负整数(均不超过 32,768),用以描绘矩阵 A。
输出格式
按与描述矩阵 A 相同的方式,输出将 S 中所有元素对 m 取模后得到的矩阵。
数据范围
1≤n≤30,
1≤k≤109,
1≤m<104
输入样例:
2 2 4
0 1
1 1
输出样例:
1 2
2 3
算法
(递归) O(n3log2k)
首先假设我们所求和的 A 是一个数,而不是一个矩阵。
那么我们要快速求出 S=A+A2+A3+⋯+Ak。
暴力肯定不可行,k 的最大值为 109。
其次想到通项公式,S=A+A2+A3+⋯+Ak=A×Ak−1A−1。
这里用到了除法,转到矩阵上,就是矩阵求逆,可我不会矩阵求逆鸭(wtcl),所以不可行。
我们观察一下,这玩意好像可以拆开a
设 Sk=A+A2+A3+⋯+Ak
Sk=A+A2+⋯+Ak2+Ak2+1+⋯+Ak
=A+A2+⋯+Ak2+Ak2(A+A2+⋯+Ak2)
=(1+Ak2)(A+A2+⋯+Ak2)
=(1+Ak2)Sk2
1+Ak2 可以用快速幂算,而 Sk2 可以递归算。
这样我们就可以快速求出 Sk 了。
那么在我们计算的对象是矩阵的时候呢?
同理,
Sk=A+A2+⋯+Ak2+Ak2+1+⋯+Ak
=A+A2+⋯+Ak2+Ak2(A+A2+⋯+Ak2)
=(E+Ak2)(A+A2+⋯+Ak2)
=(E+Ak2)Sk2
那么我们就能快速求出对于矩阵的 Sk 了,
但这是当 k 为偶数时的理想情况,k 是奇数的时候呢?
当然,可以用 Sk=Sk−1+Ak,但是这样我们就又需要计算一次快速幂。
我们可以用另一种拆法。
设 k=2n+1
那么 Sk=S2n+1=A+A2+⋯+A2n+1
=A+A(A+⋯+A2n)
=A+A×S2n
=A+A×Sk−1
这样我们就可以在不多求一次快速幂的情况下,求出 Sk 啦~
时间复杂度
emmm这个窝也不是很会算qwq
每次矩阵乘法是 O(n3),
递归一共 logk 层,
每层要求一次快速幂,
所以总递归的复杂度应该是 O(log2k),
那总的时间复杂度就是 O(n3log2k) 叭。
C++ 代码
这里提供两种写法:
1.结构体重载运算符写法
#include <stdio.h>
#include <string.h>
const int N=32;
int n, k, mod;
struct matrix // 矩阵结构体
{
int w[N][N]; // 存矩阵
void init(bool type) // 矩阵初始化。type 为 true 则初始化为 E,type 为 false 则初始化为 O。
{
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
if (i != j) w[i][j] = 0; // 两种矩阵的共同点:不在 i = j 对角线上的数皆为 0
else w[i][j] = type; // 不同点:在 i = j 对角线上,E 为 1,O 为 0
}
matrix operator * (const matrix &t) const // 重载矩阵乘法
{
matrix ans;
ans.init(false);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
for (int k = 1; k <= n; k ++ )
ans.w[i][j] = (ans.w[i][j] + t.w[i][k] * w[k][j]) % mod;
return ans;
}
matrix operator + (const matrix &t) const // 重载矩阵加法
{
matrix ans;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
ans.w[i][j] = (w[i][j] + t.w[i][j]) % mod;
return ans;
}
void print() // 将该矩阵输出
{
for (int i = 1; i <= n; i ++ ) // 不写奇怪的 for 循环了 2333
{
for (int j = 1; j <= n; j ++ )
printf("%d ", w[i][j]);
puts("");
}
}
void read() // 读入该矩阵
{
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
{
scanf("%d", &w[i][j]);
w[i][j] %= mod;
}
}
};
matrix E, v; // E 即 E 矩阵,v 为读入矩阵
matrix operator ^ (matrix a, int k) // 重载矩阵快速幂。由于要用到乘法,所以在结构体外重载。
{
matrix ans;
ans.init(true);
while (k)
{
if (k & 1) ans = ans * a;
a = a * a, k >>= 1;
}
return ans;
}
matrix S(int k) // 计算 S[k]
{
if (k == 1) return v; // 如果 k 为 1,那么返回 v,终止递归。
if (k & 1) return v + v * S(k - 1); // 如果 k 是奇数,那么返回上述 A + A * S(k - 1)
return (E + (v ^ k >> 1)) * S(k >> 1); // 否则返回上述 (E + A ^ (k / 2)) * S(k / 2)
}
int main()
{
scanf("%d%d%d", &n, &k, &mod); // 读入三个数值
E.init(true); // 初始化矩阵 E
v.read(); // 读入矩阵 v
S(k).print(); // 计算结果 S(k) 并输出
return 0;
}
2.纯数组模拟写法
#include <stdio.h>
#include <string.h>
const int N = 31;
int n, k;
int mod;
int E[N][N];
int w[N][N];
void init(int v[][N], bool type) // 初始化,同上。
{
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
v[i][j] = i == j ? type : 0;
}
void add(int A[][N], int B[][N]) // 计算矩阵 A + B 的结果,并存储在 A 中。
{
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
A[i][j] = (A[i][j] + B[i][j]) % mod;
}
void multi(int A[][N], int B[][N]) // 计算矩阵 A * B 的结果,并存储在 A 中。
{
static int ans[N][N];
init(ans, false);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
for (int k = 1; k <= n; k ++ )
ans[i][j] = (ans[i][j] + A[i][k] * B[k][j]) % mod;
memcpy(A, ans, sizeof ans);
}
void qmi(int A[][N], int k) // 计算 A 的 k 次方的结果,并存储在 A 中。
{
static int ans[N][N];
init(ans, true);
while (k)
{
if (k & 1) multi(ans, A);
multi(A, A);
k >>= 1;
}
memcpy(A, ans, sizeof ans);
}
void S(int k) // 计算 S[k],并存储在矩阵 w 中。
{
if (k == 1) return ;
int tmp[N][N];
memcpy(tmp, w, sizeof w); // 在计算前要先将 w 复制到 tmp 中
if (k & 1) // 如果 k 是奇数
{
S(k - 1); // 先计算 S(k - 1),并存储在矩阵 w 中。
multi(w, tmp); // 计算 w * 原w 的结果,并存储在矩阵 w 中。
add(w, tmp); // 计算 w + 原w 的结果,并存储在矩阵 w 中。
}
else
{
S(k >> 1); // 计算 S(k / 2),并存储在矩阵 w 中。
qmi(tmp, k >> 1); // 计算 原w 的 k / 2 次方,并存储在矩阵 tmp 中。
add(tmp, E); // 计算求完快速幂后的 原w + E 的结果,并存储在 tmp 中。
multi(w, tmp); // 计算 w * (E + 原w ^ (k / 2)) 的结果,并存储在 w 中。
}
}
int main()
{
scanf("%d%d%d", &n, &k, &mod);
init(E, true); // 初始化矩阵 E
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
{
scanf("%d", &w[i][j]);
w[i][j] %= mod;
}
S(k);
for (int i = 1; i <= n; i ++ )
{
for (int j = 1; j <= n; j ++ )
printf("%d ", w[i][j]);
puts("");
}
return 0;
}
给两种写法是真的很细心的一个人了 。
讲的清楚还有两种写法,tql%%%%%%%%%
在这个过程中w都已经不是原来输入时的w了 为什么还可以把w带入直接计算w^(n/2)
请问下,代码里面的矩阵乘法的顺序跟讲解里的好像不太一样,请问什么情况下矩阵乘法AB=BA 呢
求一个矩阵的高次幂的时候。An−1×A=A×An−1。
公式里 Sk=(E+Ak2)Sk2
但是代码中
S(k >> 1); qmi(tmp, k >> 1); add(tmp, E); multi(w, tmp);
Sk2项在前
把公示用分配律拆开 ES^{k/2}+A^{k/2}S^{k/2} 左边是乘单位矩阵满足交换律 右边用分配律搭配楼主上面给的式子变成A∗Ak/2+•••+Ak/2∗Ak/2再用分配律整个式子就成了Sk/2∗E+Sk/2∗Ak/2最后套分配律就行了
hack:
2 1 1
0 1
1 1
dalao的代码初始化没有模mod
收到,已修正。
Orz
sdl,awsl
tql
Orz