矩阵快速幂优化 DP(最全!)
2026-07-19 21:30:27
发布于:北京
矩阵快速幂优化 DP
1. 矩阵快速幂优化 DP 的定义
若目标 DP 满足线性和马尔可夫性,存在 维状态向量 和 转移矩阵 ,满足
则
因为矩阵乘法具有结合律,所以可以利用快速幂将 DP 的时间复杂度优化至 。
2. 一般线性递推的矩阵构建
对于递推
状态向量为
则转移矩阵为
初始向量取 。则
示例代码:
#include<iostream>
using namespace std ;
const int K = 2 ;
const long long MOD = 1e9+7 ;
// c = a * b,并取模
void mul(long long a[K][K], long long b[K][K], long long c[K][K]){
for(int i = 0 ; i < K ; i++){
for(int j = 0 ; j < K ; j++){
long long sum = 0 ;
for(int t = 0 ; t < K ; t++){
sum = (sum + a[i][t] * b[t][j]) % MOD ;
}
c[i][j] = sum ;
}
}
}
// ans = M^n
void pow(long long M[K][K] , long long n , long long ans[K][K]) {
long long p[K][K] , t[K][K] ;
for (int i = 0 ; i < K ; i++){
for (int j = 0 ; j < K ; j++){
p[i][j] = M[i][j] ;
}
}
// 初始化 ans 为单位矩阵
for(int i = 0 ; i < K ; i++){
for(int j = 0 ; j < K ; j++){
ans[i][j] = (i == j) ;
}
}
while(n){
if(n & 1){
mul(ans, p, t) ;
for(int i = 0 ; i < K ; i++){
for(int j = 0 ; j < K ; j++){
ans[i][j] = t[i][j] ;
}
}
}
n >>= 1 ;
mul(p, p , t) ;
for(int i = 0 ; i < K ; i++){
for(int j = 0 ; j < K ; j++){
p[i][j] = t[i][j] ;
}
}
}
}
int main() {
long long n ;
cin >> n ;
if(n <= 1){
cout << n << endl ;
return 0 ;
}
// 以斐波那契数列为例
// dp[i] = 1*dp[i-1] + 1*dp[i-2]
// 转移矩阵: M = 1 1
// 1 0
long long M[K][K] = {{1 , 1} , {1 , 0}} ;
long long res[K][K] ;
pow(M , n - 1 , res) ;
// 斐波那契数列的初始向量使得结果恰好为 res[0][0]
// cout << res[0][0] << endl ;
// 作为一般性示例,依然保留初始向量部分
long long v[K] = {1 , 0} ;
long long ans[K] = {0 , 0} ;
// 乘初始向量
for(int i = 0 ; i < K ; i++){
for(int j = 0 ; j < K ; j++){
ans[i] = (ans[i] + res[i][j] * v[j]) % MOD ;
}
}
cout << ans[0] << endl;
return 0;
}
3. 非齐次多项式驱动项的增广矩阵
设递推包含 次多项式驱动项 :
将 按 的升幂展开为 。取多项式基底为升幂排列,构造增广状态向量
总维度为 。递推写作 ,转移矩阵具有分块形式
右下角块为 下三角矩阵,其中的第 行第 列为 ,,当 时元素为 。该块将 映射为 。
递推从 开始,所有负数下标的 均视为 。初始向量取
则
示例代码:
#include<iostream>
using namespace std ;
const int K = 5 ; // k = 2 , d = 2 -> K = k + d + 1 = 5
const long long MOD = 1e9+7 ;
// c = a * b,并取模
void mul(long long a[K][K], long long b[K][K], long long c[K][K]){
for(int i = 0 ; i < K ; i++){
for(int j = 0 ; j < K ; j++){
long long sum = 0 ;
for(int t = 0 ; t < K ; t++){
sum = (sum + a[i][t] * b[t][j]) % MOD ;
}
c[i][j] = sum ;
}
}
}
// ans = M^n
void pow(long long M[K][K] , long long n , long long ans[K][K]) {
long long p[K][K] , t[K][K] ;
for (int i = 0 ; i < K ; i++){
for (int j = 0 ; j < K ; j++){
p[i][j] = M[i][j] ;
}
}
// 初始化 ans 为单位矩阵
for(int i = 0 ; i < K ; i++){
for(int j = 0 ; j < K ; j++){
ans[i][j] = (i == j) ;
}
}
while(n){
if(n & 1){
mul(ans, p, t) ;
for(int i = 0 ; i < K ; i++){
for(int j = 0 ; j < K ; j++){
ans[i][j] = t[i][j] ;
}
}
}
n >>= 1 ;
mul(p, p , t) ;
for(int i = 0 ; i < K ; i++){
for(int j = 0 ; j < K ; j++){
p[i][j] = t[i][j] ;
}
}
}
}
int main() {
// 递推式: dp[i] = 2dp[i-1] + dp[i-2] + 3i^2
// 设 dp[0] = 0 , dp[1] = 1
long long n ;
cin >> n ;
if(n == 0){cout << 0 << endl ;return 0 ;}
if(n == 1){cout << 1 << endl ;return 0 ;}
// 状态向量 V_i = [dp[i], dp[i-1], 1, i, i^2]^T
// P(i+1) = 3(i+1)^2 = 3 + 6i + 3i^2 -> q0=3, q1=6, q2=3
// 转移矩阵,按照第 3 节构造
long long M[K][K] = {
{2, 1, 3, 6, 3}, // 新 dp
{1, 0, 0, 0, 0}, // dp 下移
{0, 0, 1, 0, 0}, // 1 不变
{0, 0, 1, 1, 0}, // i -> i+1 = 1+i
{0, 0, 1, 2, 1} // i^2 -> (i+1)^2 = 1+2i+i^2
};
long long res[K][K] ;
pow(M, n - 1, res) ;
// 初始向量 V_1 (i=1): dp[1]=1, dp[0]=0, 1, i=1, i^2=1
long long v[K] = {1, 0, 1, 1, 1} ;
long long ans[K] = {0} ;
// 乘初始向量
for(int i = 0 ; i < K; i++){
for(int j = 0 ; j < K; j++){
ans[i] = (ans[i] + res[i][j] * v[j]) % MOD ;
}
}
cout << ans[0] << endl ;
return 0;
}
4. 前缀和累加器的嵌入矩阵
对线性递推 ,需要同时维护前缀和 。定义增广状态向量
维度为 。递推关系对 成立,转移矩阵为
最后一行复制系数 以计算新的 ,末列的 将旧前缀和 累加到 中。
初始向量取
其中 。则对 ,
示例代码:
#include<iostream>
using namespace std ;
const int K = 3 ; // k = 2 + 前缀和嵌入 -> K = k + 1 = 3
const long long MOD = 1e9+7 ;
// c = a * b,并取模
void mul(long long a[K][K], long long b[K][K], long long c[K][K]){
for(int i = 0 ; i < K ; i++){
for(int j = 0 ; j < K ; j++){
long long sum = 0 ;
for(int t = 0 ; t < K ; t++){
sum = (sum + a[i][t] * b[t][j]) % MOD ;
}
c[i][j] = sum ;
}
}
}
// ans = M^n
void pow(long long M[K][K] , long long n , long long ans[K][K]) {
long long p[K][K] , t[K][K] ;
for (int i = 0 ; i < K ; i++){
for (int j = 0 ; j < K ; j++){
p[i][j] = M[i][j] ;
}
}
// 初始化 ans 为单位矩阵
for(int i = 0 ; i < K ; i++){
for(int j = 0 ; j < K ; j++){
ans[i][j] = (i == j) ;
}
}
while(n){
if(n & 1){
mul(ans, p, t) ;
for(int i = 0 ; i < K ; i++){
for(int j = 0 ; j < K ; j++){
ans[i][j] = t[i][j] ;
}
}
}
n >>= 1 ;
mul(p, p , t) ;
for(int i = 0 ; i < K ; i++){
for(int j = 0 ; j < K ; j++){
p[i][j] = t[i][j] ;
}
}
}
}
int main() {
// 斐波那契数列求和
// dp[i] = dp[i-1] + dp[i-2]
// dp[0] = 0 , dp[1] = 1
// 状态向量 V_i = [dp[i], dp[i-1], S[i]]^T
long long n ;
cin >> n ;
if(n == 0){cout << 0 << endl ;return 0 ;}
if(n == 1){cout << 1 << endl ;return 0 ;}
// 转移矩阵,按照第 4 节构造,k=2, c1=1, c2=1
long long M[K][K] = {
{1, 1, 0}, // 新 dp[i+1] = 1*dp[i] + 1*dp[i-1]
{1, 0, 0}, // dp[i] 下移
{1, 1, 1} // 新 S[i+1] = 新dp + S[i] = 1*dp[i]+1*dp[i-1] + 1*S[i]
};
long long res[K][K] ;
pow(M, n - 1, res) ;
// 初始向量 V_1 (i=1): dp[1]=1, dp[0]=0, S[1]=dp[1]=1
long long v[K] = {1, 0, 1} ;
long long ans[K] = {0} ;
// 乘初始向量
for(int i = 0 ; i < K; i++){
for(int j = 0 ; j < K; j++){
ans[i] = (ans[i] + res[i][j] * v[j]) % MOD ;
}
}
// 前缀和存于 ans[2]
cout << ans[2] << endl ;
return 0;
}
5. 高阶依赖与拆点构造
当问题依赖过去的状态,仅记录当前点将无法判断下一步的合法性,需要将状态重新定义为边,以补齐所依赖的历史信息,恢复马尔可夫性。在此类图问题中,前几步所走的边是所需历史信息的载体。
设边集为 ,状态向量
其中 表示第 步结束时恰落在边 终点的路径数。转移矩阵 是 的 矩阵,其元素定义为
这里 分别表示边 的起点与终点。递推式为 。
初始向量 中,以起点出发的边对应的分量为 ,其余分量为 。则长度为 的路径中,到达目标顶点 的路径总数为
#include<iostream>
using namespace std ;
const int K = 4 ; // 边的数量 E = 4
const long long MOD = 1e9+7 ;
// c = a * b,并取模
void mul(long long a[K][K], long long b[K][K], long long c[K][K]){
for(int i = 0 ; i < K ; i++){
for(int j = 0 ; j < K ; j++){
long long sum = 0 ;
for(int t = 0 ; t < K ; t++){
sum = (sum + a[i][t] * b[t][j]) % MOD ;
}
c[i][j] = sum ;
}
}
}
// ans = M^n
void pow(long long M[K][K] , long long n , long long ans[K][K]) {
long long p[K][K] , t[K][K] ;
for (int i = 0 ; i < K ; i++){
for (int j = 0 ; j < K ; j++){
p[i][j] = M[i][j] ;
}
}
// 初始化 ans 为单位矩阵
for(int i = 0 ; i < K ; i++){
for(int j = 0 ; j < K ; j++){
ans[i][j] = (i == j) ;
}
}
while(n){
if(n & 1){
mul(ans, p, t) ;
for(int i = 0 ; i < K ; i++){
for(int j = 0 ; j < K ; j++){
ans[i][j] = t[i][j] ;
}
}
}
n >>= 1 ;
mul(p, p , t) ;
for(int i = 0 ; i < K ; i++){
for(int j = 0 ; j < K ; j++){
p[i][j] = t[i][j] ;
}
}
}
}
int main() {
// 示例问题:
// e0: A->B (红)
// e1: A->C (蓝)
// e2: B->C (蓝)
// e3: C->A (红)
// 不能连续走同色的边
// 求从 A 出发,走 n 步,回到 A 的路径数
long long n ;
cin >> n ;
if(n == 0 || n == 1){cout << 0 << endl ;return 0 ;}
// 转移矩阵 M[i][j] 表示是否可以从边 j 走到边 i,可以为1,不可以为0
// 行i,列j
long long M[K][K] = {
{0, 0, 0, 0}, // e0
{0, 0, 0, 1}, // e1
{1, 0, 0, 0}, // e2
{0, 1, 1, 0} // e3
};
long long res[K][K] ;
pow(M, n - 1, res) ;
// 初始向量 V_1:第一步从 A 出发,可走 e0 或 e1
long long v[K] = {1, 1, 0, 0} ;
long long ans[K] = {0} ;
// 乘初始向量
for(int i = 0 ; i < K; i++){
for(int j = 0 ; j < K; j++){
ans[i] = (ans[i] + res[i][j] * v[j]) % MOD ;
}
}
// 终点为 A 的边是 e3
cout << ans[3] << endl ;
return 0;
}
6. 多维状态压缩的向量化矩阵
若 DP 的第二维是有限状态 ,且转移形如
则可视为一阶递推,将第二维展平为向量
转移矩阵 是 的邻接矩阵,其元素为
权值 直接编码状态机的一切转移,常取 或 。递推写作 。
初始向量 由初始分布 直接给出。则
此式对应于一般形式中取 的特例。
#include<iostream>
using namespace std ;
// 题目:
// 给定 N 个节点 M 条有向边(可能有重边),求从 S 到 T 恰好经过 k 条边的路径数,模 1e9+7。
// N <= 50, k <= 1e18
const int K = 55 ; // 最大节点数
const long long MOD = 1e9+7 ;
// c = a * b,并取模
void mul(long long a[K][K], long long b[K][K], long long c[K][K]){
for(int i = 0 ; i < K ; i++){
for(int j = 0 ; j < K ; j++){
long long sum = 0 ;
for(int t = 0 ; t < K ; t++){
sum = (sum + a[i][t] * b[t][j]) % MOD ;
}
c[i][j] = sum ;
}
}
}
// ans = M^n
void pow(long long M[K][K] , long long n , long long ans[K][K]) {
long long p[K][K] , t[K][K] ;
for (int i = 0 ; i < K ; i++){
for (int j = 0 ; j < K ; j++){
p[i][j] = M[i][j] ;
}
}
// 初始化 ans 为单位矩阵
for(int i = 0 ; i < K ; i++){
for(int j = 0 ; j < K ; j++){
ans[i][j] = (i == j) ;
}
}
while(n){
if(n & 1){
mul(ans, p, t) ;
for(int i = 0 ; i < K ; i++){
for(int j = 0 ; j < K ; j++){
ans[i][j] = t[i][j] ;
}
}
}
n >>= 1 ;
mul(p, p , t) ;
for(int i = 0 ; i < K ; i++){
for(int j = 0 ; j < K ; j++){
p[i][j] = t[i][j] ;
}
}
}
}
int main(){
int n , m ;
long long k ;
int s , t ;
cin >> n >> m >> k >> s >> t ;
// 构建转移矩阵 M (列 -> 行)
// M[v][u] = 从节点 u 到节点 v 的路径数
long long M[K][K] = {0} ;
for(int i = 0 ; i < m ; i++){
int u , v ;
cin >> u >> v ;
M[v][u] = M[v][u] + 1 ;
}
long long Mk[K][K] ;
pow(M, k, Mk) ;
// 初始向量 V0:只有起点 S 处为 1,其余为 0
long long V0[K] = {0} ; V0[s] = 1 ;
long long Vk[K] = {0} ;
for(int i = 1 ; i <= n ; i++){
for(int j = 1 ; j <= n ; j++){
Vk[i] = (Vk[i] + Mk[i][j] * V0[j]) % MOD ;
}
}
// 到达终点 T 的路径数
cout << Vk[t] << endl ;
return 0 ;
}
这里空空如也













有帮助,赞一个