矩阵相关
矩阵乘法
基础部分
e.g.
规则
第一个矩阵的列数必须等于第二个矩阵的行数。
运算律
-
这条性质也是矩阵快速幂的前提
将 的程序通过矩阵优化转化至矩阵快速幂,变成 的复杂度( 是矩阵大小)。
例题——矩阵加速数列生成
P1962 斐波那契数列
P2044 [NOI2012] 随机数生成器
矩阵快速幂优化 dp
P5343【XR-1】分块
令可行的块长为 ,则转移方程显然:
但是显然这个复杂度是不可接受的,所以我们考虑优化。
考虑到块长的范围只有 ,可以利用矩阵快速幂加速解决本题。
令 表示可行的块长中最长的
设状态矩阵为
要转移到
我们要构造一个 的矩阵来转移
首先,也是一般矩阵加速 dp 的相通之处,观察到 到 这部分是不变的,那么我们可以先向转移矩阵内填入:
思考第一行填入什么,观察状态转移方程得, 应该由 转移而来,所以矩阵的第一行显而易见是:
处填 ,其余地方填 。
可得
快速幂转移即可,最终答案是答案矩阵的最上方
P3758 [TJOI2017]可乐
考虑一个更一般的情况,给定一张 个点无边权有向图,问从 经过 条边走向 的方案总数?
设矩阵 是这张图的邻接矩阵,则 中 表示 到 经过 条边的行进路线种数,理解见下:
令 ,,,则根据矩阵乘法的定义,
显然结论是正确的。可以通过矩阵快速幂加速。
再回到这道题上,在原地停留显然可以自环,而自爆可以看作连一条通向 的单向边,也就是说去了另一个世界(雾
代码就十分简单了。
P3977 [TJOI2015]棋盘
这篇文章是给像我一样初学矩阵优化 dp,然后看其他大佬写的题解看的一脸雾水的蒟蒻看的,所以比较详细,语文水平不好还请多多包涵
先用状压 dp 的思路分析,每一行的摆放状态可以压成一个二进制数,根据给出矩阵的第二行可以预处理出每一行合法的状态集合 ,再 dfs / 瞎搞出相邻行的合法转移,设 表示第 行的状态是 时的方案总数, 表示上一行的状态为 ,下一行的状态为 是否合法,则转移方程显然:
问题是,这样转移的时间复杂度为 ,大概是在 4e9 的级别,无法接受。
注意到每一层的转移都是相同的,于是我们可以考虑用矩阵快速幂优化。
为了方便描述,我们将 构成的序列称作 ,设矩阵 中 表示状态 经过 行转移到状态 的方案总数,则初始矩阵为
再来看转移矩阵,考虑等式 ,令 ,,则我们想做到 ,注意到这个式子恰好符合矩阵乘法的定义,所以转移矩阵与初始矩阵相同,接下来矩阵快速幂即可。
注意到第 行到第 行转移了 次,所以指数要等于 。
Code
#include<bits/stdc++.h>
#define int unsigned int
const int maxn = 1000006, maxm = 6;
int n, m, p, K, len, siz;
int att[3];
std :: vector<int> fst;
int cal(int x, int p, int ik) {
if(p <= ik)
return x << (ik - p);
return x >> (p - ik);
}
bool check(int x) {
int tmp = x;
for(int i = 0; tmp >> i; ++ i) {
if((x & (1 << i)) == 0) continue;
if((x & cal(att[1], p, i + K)) & ((len - 1) ^ (1 << i)))
return false;
}
return true;
}//一行内是否合法
bool con(int x, int y) {
x ^= y ^= x ^= y;
int tmp = x;
for(int i = 0; i < m; ++ i) {
if((x & (1 << i)) == 0) continue;
if(y & cal(att[2], p, i + K))
return false;
}
tmp = y;
for(int i = 0; i < m; ++ i) {
if((y & (1 << i)) == 0) continue;
if(x & cal(att[0], p, i + K))
return false;
}
return true;
}//相邻两行是否合法
struct matrix {
int m[(1 << maxm) + 5][(1 << maxm) + 5], a, b;
matrix (int x, int y) {
a = x; b = y;
for(int i = 1; i <= a; ++ i)
for(int j = 1; j <= b; ++ j)
m[i][j] = 0;
}
matrix friend operator * (matrix x, matrix y) {
matrix ans(x.a, y.b);
for(int i = 1; i <= x.a; ++ i)
for(int k = 1; k <= y.a; ++ k) {
int s = x.m[i][k];
for(int j = 1; j <= y.b; ++ j)
ans.m[i][j] = (ans.m[i][j] + s * y.m[k][j]);
}
return ans;
}
};
matrix ksm(matrix base, int p) {
matrix res(siz, siz);
for(int i = 1; i <= siz; ++ i) res.m[i][i] = 1;
while(p) {
if(p & 1) res = base * res;
base = base * base; p >>= 1;
}
return res;
}
signed main() {
std :: cin >> n >> m >> p >> K; ++ K;
len = 1 << m;
for(int i = 0; i < 3; ++ i)
for(int j = 1, tmp; j <= p; ++ j)
att[i] <<= 1, std :: cin >> tmp, att[i] |= tmp;
fst.push_back(0x7fffffff);
for(int i = 0; i < len; ++ i)
if(check(i)) fst.push_back(i);
siz = fst.size() - 1;
matrix zy(siz, siz);
for(int i = 1; i <= siz; ++ i)
for(int j = 1; j <= siz; ++ j)
if(con(fst[i], fst[j]))
zy.m[i][j] = 1;
matrix res = ksm(zy, n - 1);
int ans = 0;
for(int i = 1; i <= siz; ++ i) {
for(int j = 1; j <= siz; ++ j)
ans += res.m[i][j];
}
std :: cout << ans;
return 0;
}
P3216 [HNOI2011]数学作业
设 为 连起来得到的数,则显然可以得到:,其中 表示 的位数,可以发现对于位数相同的数来说,这个转移方程是固定的,于是考虑用矩阵优化:对于位数为 的数,有如下转移:
分段转移即可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
· Manus的开源复刻OpenManus初探