矩阵快速幂优化dp

矩阵乘法

算法学习

模板学习

前言: 矩阵快速幂可以优化 d p dp dp ,加快递推式。

矩阵乘法: C i j = ∑ k = 1 n A i k × B k j C_{ij}=\sum_{k=1}^n A_{ik}\times B_{kj} Cij=k=1nAik×Bkj

模板: P 3390 P3390 P3390

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
#define X 2
const int P=1e9+7;
struct Mat {
    vector<vector<LL> >a;
    Mat(){
        a.resize(X,vector<LL>(X,0));
    }
    Mat(vector<vector<LL> >b){
        a=b;
    }
	Mat one(){
        Mat a;
		for(int i=0;i<X;i++)a.a[i][i]=1;
        return a;
	}
	Mat operator *(const Mat &obj){
		Mat ret;
		for(int i=0; i<X; i++) {
			for(int j=0; j<X; j++) {
				for(int k=0; k<X; k++) {
					ret.a[i][j]=(ret.a[i][j]+a[i][k]*obj.a[k][j])%P;
				}
			}
		}
		return ret;
	}
    Mat ksm(Mat a,LL b){
        Mat ret=one();
        while(b){
            if(b&1)ret=ret*a;
            a=a*a;
            b>>=1;
        }
        return ret;
    }
};

优化思路

A t = A t − 1 × C = > A t = A 1 × C t − 1 A_t=A_{t-1}\times C=>A_t=A_1\times C^{t-1} At=At1×C=>At=A1×Ct1

由于符合分配律,因此可以通过计算 C t − 1 C^{t-1} Ct1 来加速计算 A t A_t At

时间复杂度: O ( t n 3 ) O(tn^3) O(tn3)

博客

十个利用矩阵乘法解决的经典题目 | Matrix67: The Aha Moments

练习1:加速dp递推式

例题1:P1962

题目描述: f 1 = f 2 = 1 , f i = f i − 1 + f i − 2 f_1=f_2=1,f_i=f_{i-1}+f_{i-2} f1=f2=1,fi=fi1+fi2 。求 f n ( m o d m o d ) f_n\pmod {mod} fn(modmod) n < 2 63 n<2^{63} n<263

问题分析:

  • ( f i , f i + 1 ) × [ 0 1 1 1 ] = ( f i + 1 , f i + 2 ) (f_i,f_{i+1})\times \left[\begin{matrix}0&1\\1&1\end{matrix}\right]=(f_{i+1},f_{i+2}) (fi,fi+1)×[0111]=(fi+1,fi+2)
  • 即可得到: ( f n , f n + 1 ) = ( f 1 , f 2 ) × [ 0 1 1 1 ] n − 1 (f_n,f_{n+1})=(f_1,f_2)\times \left[\begin{matrix}0&1\\1&1\end{matrix}\right]^{n-1} (fn,fn+1)=(f1,f2)×[0111]n1
void solve(){
    LL n;
    cin>>n;
    Mat a({{0,1},{1,1}});
    a=a.ksm(a,n-1);
    cout<<(a.a[0][0]+a.a[1][0])%P<<'\n';
}

例题2:P2044

题目描述: 给定 f 0 f_0 f0 ,已知 f i = ( t × f n − 1 + p ) ( m o d P 1 ) f_i=(t\times f_{n-1}+p)\pmod {P1} fi=(t×fn1+p)(modP1) 。求 f ( n ) ( m o d P 2 ) f(n)\pmod {P2} f(n)(modP2) n , P 1 ∈ [ 1 , 1 e 18 ] , t , p , f 0 ∈ [ 0 , 1 e 18 ] , P 2 ∈ [ 1 , 1 e 8 ] n,P1\in[1,1e18],t,p,f_0\in[0,1e18],P2\in[1,1e8] n,P1[1,1e18],t,p,f0[0,1e18],P2[1,1e8]

问题分析:

  • ( f i , f i + 1 ) × [ t 0 1 1 ] = ( f i + 1 , f i + 2 ) (f_i,f_{i+1})\times \left[\begin{matrix}t&0\\1&1\end{matrix}\right]=(f_{i+1},f_{i+2}) (fi,fi+1)×[t101]=(fi+1,fi+2)

例题3:P1939

题目描述: f 1 = f 2 = f 3 = 1 , f i = f i − 1 + f i − 3 f_1=f_2=f_3=1,f_i=f_{i-1}+f_{i-3} f1=f2=f3=1,fi=fi1+fi3 。求 f ( n ) ( m o d 1 e 9 + 7 ) f(n)\pmod{1e9+7} f(n)(mod1e9+7) T < = 100 , 1 < = n < = 1 e 9 T<=100,1<=n<=1e9 T<=100,1<=n<=1e9

问题分析:

  • ( f i , f i + 1 , f i + 2 ) × [ 0 0 1 1 0 0 0 1 1 ] = ( f i + 1 , f i + 2 , f i + 3 ) (f_i,f_{i+1},f_{i+2})\times \left[\begin{matrix}0&0&1\\1&0&0\\0&1&1\end{matrix}\right]=(f_{i+1},f_{i+2},f_{i+3}) (fi,fi+1,fi+2)× 010001101 =(fi+1,fi+2,fi+3)
void solve(){
    LL n;
    cin>>n;
    Mat a({{0,0,1},{1,0,0},{0,1,1}});
    a=a.ksm(a,n-1);
    cout<<(a.a[0][0]+a.a[1][0]+a.a[2][0])%P<<'\n';
}

图上加速

例题1:

题目描述: 给定 n n n 个点 m m m 条边的有向图。求 t t t 秒后, 1 1 1 n n n 的不同路径数有多少种。

问题分析:

  • A A A 为邻接矩阵,其中 A i j = 1 A_{ij}=1 Aij=1 表示存在 i − > j i->j i>j 的边。
  • C i j = ∑ k = 1 n A i k × A k j C_{ij}=\sum_{k=1}^nA_{ik}\times A_{kj} Cij=k=1nAik×Akj ,则 C i j C_{ij} Cij 表示 i i i j j j 经过两条边的路径数量。
  • A t [ 1 ] [ n ] A^t[1][n] At[1][n] 就是答案。

例题2:P5789

题目描述: 给定 n n n 个点 m m m 条边的无向图。初始在点 1 1 1 ,没秒钟可能会发生:爆炸,原地不动,从某条边走向下一个点。求 t t t 秒后。行为方案数有多少种。答案对 2017 2017 2017 取模。

问题分析:

  • 每个点连个自环,来维护:原地不同
  • 每个点向 0 0 0 连边并让 0 0 0 连一个自环,来维护:爆炸。
  • 然后让套路做即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int P=2017;
int n,m;
struct Mat {
    vector<vector<LL> >a;
    Mat(){
        a.resize(n+1,vector<LL>(n+1,0));
    }
    Mat(vector<vector<LL> >b){
        a=b;
    }
	Mat one(){
        Mat a;
		for(int i=0;i<=n;i++)a.a[i][i]=1;
        return a;
	}
	Mat operator *(const Mat &obj){
		Mat ret;
		for(int i=0; i<=n; i++) {
			for(int j=0; j<=n; j++) {
				for(int k=0; k<=n; k++) {
					ret.a[i][j]=(ret.a[i][j]+a[i][k]*obj.a[k][j])%P;
				}
			}
		}
		return ret;
	}
    Mat ksm(Mat a,LL b){
        Mat ret=one();
        while(b){
            if(b&1)ret=ret*a;
            a=a*a;
            b>>=1;
        }
        return ret;
    }
};
void solve(){
    cin>>n>>m;
    vector<vector<LL> >a(n+1,vector<LL>(n+1));
    for(int i=1;i<=m;i++){
        int u,v;
        cin>>u>>v;
        a[u][v]=1;
        a[v][u]=1;
    }
    for(int i=0;i<=n;i++)a[i][i]=1;
    for(int i=1;i<=n;i++)a[i][0]=1;
    Mat x(a);
    LL t;
    cin>>t;
    x=x.ksm(x,t);
    LL ans=0;
    for(int i=0;i<=n;i++)ans=(ans+x.a[1][i])%P;
    cout<<ans;
}
### 使用矩阵快速幂优化01背包问题 通常情况下,01背包问题是通过动态规划来求解的。然而,在特定条件下,可以通过引入矩阵乘法和快速幂运算进一步优化算法效率。 #### 动态规划基础回顾 对于标准的01背包问题,定义`dp[i][j]`表示前i件物品放入容量为j的背包可以获得的最大价值。状态转移方程如下: ```java if (w + items[i].weight <= maxWeight) { dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-items[i].weight]+items[i].value); } else { dp[i][j] = dp[i-1][j]; } ``` 此方法的时间复杂度为O(nW),其中n是物品数量,W是最大重量[^2]。 #### 矩阵快速幂的应用场景分析 当面对重复子结构且具有线性关系的状态转换时,可以考虑使用矩阵加速计算过程。但是需要注意的是,并不是所有的DP都可以直接套用这种方法;只有那些能够转化为形如f(n)=Af(n−1)+B的形式才能适用。 对于经典的01背包来说,其状态转移不具备上述特征,因此无法简单地应用矩阵快速幂来进行优化。不过有一种特殊情况下的变种被称为“完全背包”,即每种类型的物品有无限多个可用,此时确实存在一种基于矩阵的方式去处理它[^4]。 #### 完全背包问题中的矩阵快速幂优化思路 假设有一个一维数组`v[]`代表不同种类商品的价值向量,以及另一个相同长度的一维布尔型数组`b[]`标记哪些位置已经被选中过。那么我们可以构建一个二阶方阵M: | v[0]*b[0] | ... | v[k-1]*b[k-1] | |-----------|-----|---------------| | . | ... | . | | v[(k-1)]*b[(k-1)] |...| v[0]*b[0] | 这里每一行都是上一行循环右移一位的结果。这样做的目的是为了模拟每次选择一件新物品加入到已有的组合里的情况。接着就可以利用这个特殊的矩阵形式配合快速幂技巧高效完成多次迭代操作了。 #### Python代码实现示例 下面给出一段Python伪代码展示如何运用矩阵快速幂解决简化版的完全背包问题(注意这并不是针对原问提到的标准01背包): ```python import numpy as np def matrix_mult(A, B): """Matrix multiplication""" return [[sum(a * b for a,b in zip(row,col)) % MOD for col in zip(*B)] for row in A] def power(M, p): result = [[int(i==j)%MOD for j in range(len(M))]for i in range(len(M))] while p > 0: if p%2 == 1: result = matrix_mult(result,M) M = matrix_mult(M,M) p //= 2 return result # 初始化参数 values = [...] # 物品价值列表 weights = [...] # 对应权重列表 capacity = ... modulus = ... # 构建初始矩阵 matrix_size = min(capacity//min(weights),len(values)) initial_matrix = [[0]*matrix_size for _ in range(matrix_size)] for idx,val in enumerate(values[:matrix_size]): initial_matrix[idx][(idx+1)%matrix_size]=val final_matrix = power(initial_matrix,capacity) print(sum(final_matrix[-1])%modulus) ``` 这段代码展示了如何创建并初始化所需的矩阵,然后调用辅助函数执行矩阵相乘与快速幂运算,最后输出最终结果。请注意实际编码过程中还需要根据具体题目调整细节部分[^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值