202006-4 1246 (100分详细题解,矩阵乘法+快速幂)
可以先看下csp官方的思路讲解,大思路是状态转移,先看下面s<=2的情况
1 -> 2
2 -> 4
4 -> 16
6 -> 6 64 4
16 -> 26(不考虑2,6,4,64的原因是,16可以拆分为1和6,而1->2 , 6-> 64, 6, 4,去除冗余解)
... 详细见尾部代码
s<=2共有14个数字,故可以将这个14个数字离散化到0~13,形成14*14转移矩阵,随后根据线性代数矩阵的结合律,这14个数字构成一个行向量a,可以使用矩阵快速幂,快速得到这14个数字出现的次数。由于向量a初始时为{1,0…0},故代码中直接由res矩阵的第0行充当。
当s>2时,可以进行倒推,如:464 上一次由 26操作而来。同时考虑到题目给的s是一个部分数字串,故可能无法直接倒推回s=2,故s在整个序列的情况,可能分为s前面是1,s前面是6,或者s前面不是1或6, 因为4->16 6->64,都会产生两位,所以s的首个数字可能是16中的6,以及64中的6,由于s只是其中的一部分,所以我们要在前面补上1和6。你可能回想为什么不在后面补1和6,其实也考虑了,不过在代码中用i + 1 == str.size()
来解决了。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 14, MOD = 998244353;
int n;
string S;
int id[100]; // id[x]将x映射到0~13
vector<int> vers{
1, 2, 4, 6, 16, 26, 41, 42, 44,
46, 61, 62, 64, 66
};
// 上面的数字进行运算后可以得出下面的贡献,两位数字的运算后的数字只有一个
// 如16->26(不考虑2,6,4,64的原因是,16可以拆分为1和6,而1->2 , 6->64, 6, 4)
// 故两位数字运算后只转移到前一位数字幂的后一位和后一位数字幂的前一位
// 当s的长度大于2时,我们可以根据转移的规律,逆推到s只有两位的情况。
// s在整个序列的情况,可能分为s前面是1,s前面是6,或者s前面不是1或6.
// 因为4->16 6->64,都会产生两位,所以s的首个数字可能是16中的6,以及64中的6,由于s只是其中的一部分,所以我们要补上1和6
vector<vector<int>> g{
{2}, {4}, {1, 6, 16}, {6, 4, 64}, {26},
{46}, {62}, {64}, {61}, {66}, {42},
{44}, {41}, {46}
};
int tr[N][N]; // tr的第j列是对ver[j]的贡献
void init() {
memset(id, -1, sizeof id);
for (int i = 0; i < N; i++) id[vers[i]] = i; // 映射
//求转移矩阵
for (int i = 0; i < N; i++)
for (auto x : g[i])
tr[i][id[x]] ++;
}
void mul(int c[][N], int a[][N], int b[][N]) {
static int tmp[N][N];
memset(tmp, 0, sizeof tmp);
for (int i = 0; i < N; i++) // 行
for (int j = 0; j < N; j++) // 列
for (int k = 0; k < N; k++) // 行×列
tmp[i][j] = (tmp[i][j] + (LL)a[i][k] * b[k][j]) % MOD;
memcpy(c, tmp, sizeof tmp);
}
int qmi(int k, int id) {
if (id == -1) return 0;
int res[N][N] = { 0 }, w[N][N];
memcpy(w, tr, sizeof w);
res[0][0] = 1; // 初始,使用res的首行充当0时刻的行向量
while (k) {
if (k & 1) mul(res, res, w); //res=res*w
mul(w, w, w); // w=w*w;
k >>= 1;
}
return res[0][id];
}
string get(string str) {
string res;
for (int i = 0; i < str.size(); i++)
if (str[i] == '2') res += '1';
else if (str[i] == '4') res += '2';
else if (str[i] == '1') {
if (i + 1 == str.size() || str[i + 1] == '6') res += '4', i++;
else return "";
} else {
if (i + 1 == str.size() || str[i + 1] == '4') res += '6', i++;
else return "";
}
return res;
}
int dfs(int k, string& str) {
if (str.size() <= 2) return qmi(k, id[stoi(str)]);
int res = 0;
for (string s : {"", "1", "6"}) {
auto t = get(s + str);
if (t.size()) res = (res + dfs(k - 1, t)) % MOD; // !!!!!!!
}
return res;
}
int main() {
init();
cin >> n >> S;
cout << dfs(n, S) << endl;
return 0;
}