题目描述
小Z最近发现了一种非常有趣的数,他将这种数称之为 Sam 数。Sam 数具有以下特征:相邻两位的数字之差不超过 2。小Z还将 Sam 数按位数进行了分类,他将一个 k 位 Sam 数称之为 k 阶 Sam 数。但不幸的是小Z发现他数不清第 k 阶的 Sam 数一共有多少个,这个时候机智的他想到了向你求助。
输入输出格式
输入格式:
第一行为一个整数 k,含义见题面。
输出格式:
一行一个整数 ans,表示 k 阶的 Sam 数的个数。
由于第 k 阶 Sam 数非常多,你只需要输出 ans mod 1000000007。
输入输出样例
说明
【数据规模和约定】
对于 30%的数据,1 ≤ k ≤ 10^6。
对于 60%的数据,1 ≤ k ≤ 10^12。
对于 100%的数据,1 ≤ k ≤ 10^18。
这道题很容易可以想到数位dp:dp[i][j]表示dp到第i个, 以j为末尾的情况有多少种。但是1e18的范围心态很爆炸!
以前看到10^18的数据范围想都不想直接数学方法。但是自从上次hdu5434企图用轮廓线找规律被集训队大佬嘲讽了一波之后,蒟蒻就永远记住了矩阵快速幂这种操作(如果不会矩阵快速幂可以先百度矩阵快速幂求斐波那契数列第n项)。大概意思是如果dp的每一次转移都相同,那么我们可以用矩阵快速幂无压力dp(例如hdu5434)。这道题也是类似,我们可以发现每一次dp[i - 1][j] 到 dp[i][j]的状态转移都是相同的。这样我们只要通过转移构造转移矩阵,然后对转移矩阵进行快速幂就可以了。下面是代码:
#include<cstdio>
#include<cstring>
#include<cmath>
#define LL long long
using namespace std;
LL n, ans;
const int p = 1000000007;
struct Matrix {
LL DT[130][130];
int n, m;
Matrix(int nn, int nm) {
memset(DT, 0, sizeof(DT));
n = nn, m = nm;
}
void Std(int x) {
n = m = x;
for(register int i = 0; i < x; ++i)
DT[i][i] = 1;
}
Matrix operator *(const Matrix &a) {
Matrix ans(n, a.m);
for(register int i = 0; i < n; ++i)
for(register int j = 0; j < a.m; ++j)
for(register int k = 0; k < m; ++k)
(ans.DT[i][j] += (DT[i][k] * a.DT[k][j]) % p) %= p;
return ans;
}
}trans(10, 10), st(1, 10);
Matrix fpow(Matrix a, LL b) {
Matrix ans(a.n, a.n);
ans.Std(a.n);
while(b) {
if(b & 1) ans = ans * a;
a = a * a, b >>= 1;
}
return ans;
}
int main() {
scanf("%lld", &n);
if(n == 1) {
printf("10");
return 0;
}
for(register int i = 1; i < 10; ++i)
st.DT[0][i] = 1;
for(register int i = 0; i < 10; ++i)
for(register int j = 0; j < 10; ++j)
if(abs(i - j) <= 2) ++trans.DT[i][j];
trans = st * fpow(trans, n - 1);
for(register int i = 0; i < 10; ++i)
(ans += trans.DT[0][i] % p) %= p;
printf("%lld", ans);
return 0;
}
rockdu
没有帐号? 立即注册