矩阵快速幂优化dp

寻址连续优化

for(int i = 1; i <= n; i++)
        for(int k = 1; k <= n; k++)
            if(a.a[i][k])
            for(int j = 1; j <= n; j++)
                c.a[i][j] = (c.a[i][j] + 1ll * a.a[i][k] * b.a[k][j]) % mod;

P3216 [HNOI2011]数学作业 (普通套路)

题目

\(f(i)\) 表示将 \(1\sim i\) 的数顺次拼接得到的一个数,计算 \(f(n)\mod m\)

\(n\le 1\times 10^{18},m\le 1\times 10^9\)

题解

\(k\)\(i\) 的位数,转移为

\[f[i]=f[i-1]\times 10^k+i \]

然后有

\[\begin{bmatrix} f_i\\i\\1 \end{bmatrix} \times \begin{bmatrix} {10^k}&{1}&{1}\\{0}&{1}&{1}\\{0}&{0}&{1} \end{bmatrix} \times \begin{bmatrix} f_{i-1}\\i-1\\1 \end{bmatrix} \]

CF514E Darth Vader and Tree (较大矩阵的矩阵快速幂)

题面

有一个无限大的有根树,树上的每一个节点都有 \(n\) 个子节点(\(1 \leq n \leq 10^5\)),任意一个节点它的第 \(i\) 个子节点之间的距离为 \(d_i\)\(1 \leq d_i \leq 100\))。

给出一个数 \(x\)\(0 \leq x \leq 10^9\)),求有多少个节点到根节点的距离小于等于 \(x\),输出答案对 \(10^9+7\) 取模后的结果。

题解

\(f_i\) 表示到根节点距离恰好为 \(i\) 的方案数

显然有 \(f_i=\sum_{j=1}^n f_{i-d_j}\)

实际上,我们只关心每种 \(d_i\) 有多少个,而且 \(d_i\) 只有 \(100\) 种,所以考虑离散化然后使用桶存,设 \(t_i\) 表示 \(s.t. d_k=i\)\(k\) 的个数,那么转移方程可以改写为

\[f_i=\sum_{j=1}^{100} f_{i-j}\times t_j \]

\(\text{sum}_i=\sum_{k=1}^if_k\) ,预处理完前 \(100\) 组数之后,就有转移矩阵

\[\begin{bmatrix} {1}&{t_1}&{t_2}&\cdots&{t_{100}}\\{0}&{t_1}&{t_2}&\cdots&{t_{100}}\\{0}&{1}&{0}&\cdots&{0}\\{\vdots}&{\vdots}&{\vdots}&{\ddots}&{\vdots}\\{0}&{0}&\cdots&{1}&{0} \end{bmatrix} \times \begin{bmatrix} \text{sum}_i\\ f_{i-1}\\ f_{i-2}\\ \vdots \\f_{i-100} \end{bmatrix} =\begin{bmatrix} \text{sum}_i\\ f_i\\ f_{i-1} \\ \vdots \\ f_{i-99} \end{bmatrix} \]

P2579 [ZJOI2005]沼泽鳄鱼 (邻接矩阵与矩阵快速幂)

题目

给定 \(n\) 个点的无向图,图上有 \(m\) 只鳄鱼在游荡,它们会按时间出现在不同的位置,周期只能为 \(2,3,4\)
给定起点终点,求经过 \(k\) 步后走到终点的方案数

\(n\le 50,m\le 20,k\le 2\times 10^9\)

题解

\(2, 3, 4\) 的最小公倍数为 \(12\)

所以开十二个邻接矩阵即可, 每个矩阵表示当前走一步的合法情况

\(a[0]\) 表示走 \(12\) 步的矩阵, 即 \(12\) 个邻接矩阵之积

\(\lfloor \frac{k}{12} \rfloor\)\(a[0]\) 相乘使用快速幂, 在按顺序从 \(a[1]\) 乘到 \(a[k\% 12]\)

答案为 \(a[0]^{k/12} \times a[1] \times a[2]\times \cdots s[k\%12]\)

P3176 [HAOI2015]数字串拆分(对拆分函数的处理)

题面

题解

首先,显然有

\[f_1=1,f_n=\sum_{i=\max(n-m,1)}^n f_i \]

注意到 \(f_i\) 可以化成矩阵 \(A^i\) 进行递推,而 \(f_{x+y}=A^x\times A^y=A^{x+y}\)

\(\text{num}[i][j]\) 表示从第 \(i\) 位到第 \(j\) 位所表示的数的转移矩阵,考虑到矩阵有分配律,设 \(g_i\) 表示 \(g(\text{num}[1][i])\) 所以我们有

\[g_i=\sum_{j=0}^{i-1}g_j\times A^{\text{num}[j+1][i]} \]

举个例子

\[g(12)=f(1+2)+f(12)=A^3+A^{12}=g_1\times \text{num}[2][2]+\text{num}[1][2] \]

时间复杂度 \(\mathcal{O} (n^2m^3)\)

CF576D Flights for Regular Customers (动态变化的图)

题目

  • 给定一张 \(n\) 个点 \(m\) 条边的有向图。
  • 一开始你在 \(1\) 号节点,你要走到 \(n\) 号节点去。
  • 只有当你已经走过了至少 \(d_i\) 条边时,你才能走第 \(i\) 条边。
  • 问最少要走多少条边,或判断无法到达。
  • \(n,m \le 150\)\(d_i \le 10^9\)

题解

首先对边按照 \(d_i\) 排序,从小到大依次加入每条边,动态维护能够到达的点,假设此时加入的边的 d 值为 t,矩阵加速求出当前图能够走到的所有的点

每个点的状态只有 0/1 两种,分别对应着无法到达和可以到达,因此可以 bitset 优化,时间复杂度 \(\mathcal O(\frac{n^3m \log d}w)\)

P6772 [NOI2020] 美食家 (拆点和预处理倍增矩阵)

题目

题解

首先,边长 \(w\le 5\), 不能直接快速幂计算,但是可以进行拆点

具体来说,就是将点 \(u\) 拆解为 \(u_1\rightarrow u_2\rightarrow u_3\rightarrow u_4\rightarrow u_5\),边权为 \(0\) 边长为 \(1\) 那么对于原图中的一条边 \((u,v,w)\) 我们连的边是 \(u_w\rightarrow v_1\)
边权为 \(c_u\) 边长为 \(1\) 。至此我们得到了一个 \((nw)^2\) 大小的矩阵

至于美食节的条件,我们参考 CF576D 的做法,将 \(t_i\) 进行排序,分段处理,此时时间复杂度是 \(\mathcal{O}((nw)^3\sum \log (t_i-t_{i-1}))\) 矩阵快速幂的次数太多了,无法通过本题

考虑优化,我们发现这一次次的矩阵快速幂中,每次都倍增着去做显然是有重复计算的,因此我们可以用 \(\mathcal O((nw)^3\log T)\) 的复杂度计算出倍增所需要的矩阵 \(P_i=A^{2^i}\) ,这样我们每次计算的时候就有原来快速幂时的矩阵乘矩阵优化为了向量乘矩阵,复杂度下降到了平方级别,平衡和复杂度,此时的时间复杂度是 \(\mathcal{O}((nw)^3\log T+(nw)^2\log (t_i-t_{i-1}))\) ,能够通过本题

ZR2023 CSP7连测 Day1 T3 (小模数循环节的利用)

题意

小X设想的 \(n+1\) 块土豆田排在一条直线上,从左到右编号为 \(0\)\(n\)。第i块土豆田里有 \(i\) 株各不相同的土豆,其中 \(0\) 号土豆田是土拨鼠的巢。

每一个时刻,土拨鼠会离开当前所在的土豆田去往下一块土豆田。若土拨鼠当前所在的土豆田编号为 \(i\),那么它能到达的下一块土豆田的编号 \(j\) 必须满足 \(j-i\in S\),其中 \(S\) 中只包含不大于15的正整数。若 \(i\ge 0\) ,土拨鼠在离开的同时还会破坏第 \(i\) 块土豆田中的至多一棵土豆。

小X想知道,对于给定的正整数 \(N\) ,若土拨鼠最后到达了 \(N\) 号土豆田,它有多少种破坏土豆的方案。注意,此时N号土豆田上的土豆并没有被破坏

由于答案可能很大,小X只要知道这个方案数对 \(2027\) 取模的结果。

\(n\le 10^{18},|S|\le 15\)

题解

线性dp是容易的

\[f_i=\sum_{j\in S}(i-j+1)f_{i-j} \]

即考虑从 \(i-j\) 棵土豆中拔掉哪一棵或者不拔

由于 \(|S|\) 很小,我们考虑使用矩阵乘法去写上面那个式子

\[\begin{bmatrix}i&i-1&\cdots &i-14\\i-1 & i-2&\cdots &i-15\\\vdots &\vdots&\ddots&\vdots\\i-14 & i-15 &\cdots&i-28\end{bmatrix}\begin{bmatrix}f_{i-1}\\f_{i-2}\\ \vdots \\f_{i-15}\end{bmatrix}=\begin{bmatrix}f_{i}\\f_{i-1}\\ \vdots \\f_{i-14}\end{bmatrix} \]

\(S\) 中不包含哪一部分就把那一部分的值换成 \(0\) 即可

但是这并没有什么作用,因为我们乘的矩阵每一次都不一样

注意到模数很小,而众所周知模数是可以直接模到矩阵内每一项的,因此矩阵有一个长度为 \(2027\) 的循环节,我们可以预处理出这一部分循环节矩阵乘法的结果,再对该结果做矩阵快速幂,剩余的几项暴力算即可

时间复杂度 \(\mathcal O(d^3(\text{mod} +\log n))\)

code:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
inline ll read()
{
	ll x=0,f=1;char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1;
	for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+(ch^48);
	return x*f;
}
const int mod=2027;
ll n;
int m;
bool S[16];
struct matrix{
	int z[16][16];
	inline void reset(){
		memset(z,0,sizeof(z));
	}
	matrix(){
		reset();
	}
	void init()
	{
		for(int i=1;i<=15;++i) z[i][i]=1;
	}
	void getmtx(int x)
	{
		for(int i=1;i<=15;++i)
			if(S[i]) z[1][i]=(x-i+1+mod)%mod;
		for(int i=2;i<=15;++i) z[i][i-1]=1;
	}
	matrix operator *(const matrix &w)const{
		matrix ans;
		for(int i=1;i<=15;++i)
			for(int j=1;j<=15;++j)
				for(int k=1;k<=15;++k)
					ans.z[i][j]=(ans.z[i][j]+z[i][k]*w.z[k][j]%mod)%mod;
		return ans;
	}
};
matrix ksm(matrix a,ll b)
{
	matrix res;res.init();
	while(b)
	{
		if(b&1) res=res*a;
		a=a*a;
		b>>=1;
	}
	return res;
}
int main()
{
	n=read();m=read();
	for(int i=1;i<=m;i++) S[read()]=1;
	matrix trans,ans;
	trans.init();
	ans.init();
	for(int i=1;i<=mod;i++)
	{
		matrix w;
		w.getmtx(i);
		trans=w*trans;
		if(n%mod==i)ans=trans;
	}
	ans=ans*ksm(trans,n/mod);
	cout<<ans.z[1][1]<<'\n';
	return 0;
}

posted on 2023-10-27 18:06  star_road_xyz  阅读(77)  评论(0编辑  收藏  举报

导航