原题链接: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[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;
}
原文:https://www.cnblogs.com/HotPants/p/14315067.html