归档:220813 | 社恐铁锹的第一次新知讲授:矩阵快速幂
铁锹:呃,其实我的名字是英文缩写,不是铁锹,你们不要再给我乱起外号了
因为之前已经在洛谷上写过一篇关于矩乘的总结(不过好像丢了),所以这次就接着上次写吧。
什么是矩阵?
一个 \(n\times m\) 的矩阵就是一个由数组成的 \(n\) 行 \(m\) 列的方阵。
什么是矩阵乘法?
假设有三个矩阵 \(A, B, C\),其中 \(A\) 的列数与 \(B\) 的行数相等(假设为 \(n\))。若 \(C=A\times B\),那么 \(C_{i,j}=\sum\limits_{k=1}^n A_{i,k}\times B_{k,j}\),其中 \(C\) 的行数与 \(A\) 的行数相等,\(C\) 的列数与 \(B\) 的列数相等。
什么是矩阵快速幂?
把普通快速幂的底数换成矩阵。
铁锹:呃我要讲的就是这些,同学们可以开始做题了。
呃呃,所以模拟一下这个过程就好了?
namespace Matrix {
const int maxn = 25;
template<const int mod>
class Mat {
private:
int n, m;
public:
int a[maxn][maxn];
Mat () {}
Mat (int N, int M, bool type = 0) {
n = N, m = M;
memset(a, 0, sizeof (a));
if (type) {
for (int i = 1; i <= n; ++i)
a[i][i] = 1;
}
}
int* operator[] (int q) {
return a[q];
}
const int* operator[] (int q) const {
return a[q];
}
Mat operator* (const Mat &q) const {
Mat res(n, q.m);
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= q.m; ++j) {
for (int k = 1; k <= m; ++k) {
(res[i][j] += (a[i][k]
* q[k][j] % mod + mod)
% mod) %= mod;
}
}
}
return res;
}
Mat& operator*= (const Mat &q) {
return (*this) = (*this) * q;
}
Mat operator^ (const int q) const {
int y = q;
Mat res(n, n, 1), x(*this);
while (y) {
if (y & 1)
res *= x;
x *= x;
y >>= 1;
}
return res;
}
Mat& operator^= (const int &q) {
return (*this) = (*this) ^ q;
}
};
} // namespace Matrix
A. CF450B - Jzzhu and Sequences
游戏的第一关都会放一个稀奇古怪的东西让新手入门。
——Some TFers
我看了大半天没看懂样例最后发现不是斐波那契???
那也和斐波那契一样啊。
我们都知道,斐波那契的加速矩阵是这样的:
那我们把 \(f_{i-1}\to f_{i+1}\) 的系数改成减就好了。
namespace XSC062 {
using namespace fastIO;
using namespace Matrix;
const int mod = 1e9 + 7;
int x, y, m;
Mat<mod> A, B;
int main() {
read(x);
read(y);
read(m);
if (m == 1) {
printf("%lld", (x % mod + mod) % mod);
return 0;
}
if (m == 2) {
printf("%lld", (y % mod + mod) % mod);
return 0;
}
A = Mat<mod>(1, 2);
B = Mat<mod>(2, 2);
A[1][1] = x;
A[1][2] = y;
B[1][2] = -1;
B[2][1] = B[2][2] = 1;
A *= (B ^= (m - 2));
printf("%lld", A[1][2]);
return 0;
}
} // namespace XSC062
B. CF621E - Wet Shark and Blocks
它是一道非常奇怪的题啊,为什么我倒推过不了,顺推能过啊 🤔
首先统计所有模 \(x\) 的值为 \(i\) 的个数,记为 \(cnt_i\)。
设 \(f_{i,j}\) 表示选到了第 \(i\) 组数后选的数模 \(x\) 余 \(j\) 的方案,然后滚一下,\(f_i\) 就直接表示选到某一组模 \(x\) 余 \(j\) 的方案。
那么容易得出:
然后 \(f\) 的初始值就是这个 \(cnt\) 序列啦。
但是 \(b\) 这么大,明显不可能一组一组地去推,我们就把这个式子搬到加速矩阵上面,从 \(i\) 转移到 \((i\times10+j)\bmod x\) 的位置对应的值就是 \(cnt_j\)。
然后令初始矩阵为 \(cnt\),就完了。
namespace XSC062 {
using namespace fastIO;
using namespace Matrix;
const int mod = 1e9 + 7;
const int maxn = 5e5 + 5;
Mat<mod> A, B;
int n, b, k, x, t;
int a[maxn], cnt[maxn];
inline int qkp(int x, int y, int mod) {
int res = 1;
while (y) {
if (y & 1)
(res *= x) %= mod;
(x *= x) %= mod;
y >>= 1;
}
return res;
}
int main() {
read(n);
read(b);
read(k);
read(x);
for (int i = 1; i <= n; ++i) {
read(a[i]);
++cnt[a[i] % x];
}
A = Mat<mod>(1, x, 0);
for (int i = 1; i <= x; ++i)
A[1][i] = cnt[i - 1];
B = Mat<mod>(x, x, 0);
for (int i = 0; i < x; ++i) {
for (int j = 0; j < x; ++j) {
t = (i * 10 + j) % x;
B[i + 1][t + 1] += cnt[j];
}
}
B.print();
A *= (B ^ (b - 1));
printf("%lld", A[1][k + 1]);
return 0;
}
} // namespace XSC062
—— · EOF · ——
真的什么也不剩啦 😖