CF1117D Magic Gems 矩阵快速幂 DP

原题链接:CF1117D Magic Gems

题目大意

一开始有无穷多个牛逼石头,每个牛逼石头可以分解成\(m\)个普通石头,而普通石头不能再次分解出普通石头.你有一个大小为\(n\)的背包,牛逼石头和普通石头的大小都是1个单位,问恰好填满整个背包的方案数有多少.答案对\(10^9+7\)取模

此处讨论的方案数包含牛逼石头位置不同,但是数量相同的方案,即不认为数量相同的位置不同的属于同构的方案.

数据范围:

\(1 \leq n \leq 10^{18}\)

\(2 \leq m \leq 100\)

思路

看到\(n\)大的离谱而\(m\)很小,支持\(O(m^3logn)\)很可能就是一个矩阵快速幂了,顺着可以先把递推表达式搞出来.

这里有一个比较浅显的思路就是直接\(dp\)\(f[i][j]\)表示当前一共有\(i\)个石头\(j\)个位置的方案数,不过这样去做有很多组合数上的问题不太好搞,有个更好的做法是:\(f[i]\)表示占用\(i\)个单位的方案数.那么转移也比较显然,就是枚举这个状态的最后一部分石头是怎么来的:要么从前一个状态加了一个不分解的牛逼石头,也就是\(f[i] += f[i - 1]\),要么把一个牛逼石头分解了再加进来,也就是\(f[i] += f[i - m]\)合起来就是\(f[i] = f[i - 1] + f[i - m]\).这一步比较显然,不是这个题的重点.

考虑优化,直接暴力\(dp\)显然是\(O(nm)\)的,需要矩阵快速幂:

  • 状态矩阵\(A = [f[0],f[1],f[2],...f[m-1]]\),状态矩阵是一个\(1*m\)的行向量.
  • 转移矩阵\(B\).我们希望能有这样的一个结果:\([f[0],f[1],f[2],...f[m-1]] *B = [f[1],f[2],f[3]...f[m]]\).那么可以注意到除了最后一列元素\(f[m-1]\)比较怪之外,其他的元素其实只是后面的往前面挪动了一位,对此可以先构造转移矩阵\(B\)的前\(m-1\)列,对于这些列,根据矩乘的式子可以找到就是让第\(i\)列的\(i+1\)行之外的所有元素为\(0\)而他系数是\(1\)就可以了.对于最后一列元素直接单独对他做就可以了:\(f[m]=f[0] + f[m-1]\),只需要这一列本来的元素以及最左侧的\(f[0]\)就可以了,系数就是\(B[1][m] = 1\)以及\(B[m][m]=1\).
  • 考虑转移:首先\(A*B\)的结果会使得\(A[1][1]\)的位置变成\(f[1]\),所以乘上\(B^i\)之后,\(A[1][1]=f[i]\).那么结果需要的是\(f[n]\)直接让\(A*B^n\)即可.后者可以矩阵快速幂解决.

最后答案取\(A[1][1]\)输出即可.

(由于矩乘部分也是copy别人的代码来的,不知道会不会有锅,有锅别找我(逃))

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	

const int N = 105,MOD = 1e9+7;
ll n,m;
struct Matrix
{
	ll a[N+5][N+5];
	ll* operator[](int x){return a[x];}
	Matrix operator*(Matrix& rhs){
		Matrix res;
		for(int i=1;i<=m;++i){
			for(int j=1;j<=m;++j){
				for(int k=1;k<=m;++k)
				{
					res[i][j] = (res[i][j] + (ll)a[i][k] * rhs[k][j] % MOD) % MOD;
				}
			}
		}
		return res;
	}
	Matrix()	{memset(a,0,sizeof(a));}
};
Matrix mat_pow(Matrix x,ll i)
{
	Matrix y;
	for(int j=1;j<=m;++j)y[j][j]=1;
	while(i)
	{
		if(i&1LL)y=y*x;
		x=x*x;
		i>>=1;
	}
	return y;
}

int main()
{
	scanf("%lld%lld",&n,&m);
	Matrix A,B;
	forn(i,1,m)	A.a[1][i] = 1;
	forn(i,1,m - 1)	B.a[i + 1][i] = 1;
	B.a[1][m] = 1;B[m][m] = 1;
	
	B = mat_pow(B,n);
	A = A * B;
	printf("%lld",A[1][1]);
	
    return 0;
}
posted @ 2021-01-22 20:12  随处可见的阿宅  阅读(106)  评论(0编辑  收藏  举报