先处理函数 fif_ifi,有 fi=∑j=i−mi−1fjf_i = \sum \limits _{j = i - m}^{i - 1} f_jfi=j=i−m∑i−1fj,这个递推式显然可以通过矩阵乘法进行优化。设 FiF_iFi 表示通过递推函数 fif_ifi 得到的矩阵,则有以下矩阵的递推(以 m=5m = 5m=5 为例):
Fi=[fifi−1fi−2fi−3fi−4]= [1111110000010000010000010]k⋅ [fi−1fi−2fi−3fi−4fi−5]
F_i =
\begin{bmatrix}
f_i \\
f_{i-1} \\
f_{i-2} \\
f_{i-3} \\
f_{i-4}
\end{bmatrix}
= \
\begin{bmatrix}
1 & 1 & 1 & 1 & 1 \\
1 & 0 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 & 0 \\
0 & 0 & 1 & 0 & 0 \\
0 & 0 & 0 & 1 & 0
\end{bmatrix}^k
\cdot \
\begin{bmatrix}
f_{i-1} \\
f_{i-2} \\
f_{i-3} \\
f_{i-4} \\
f_{i-5}
\end{bmatrix}
Fi=fifi−1fi−2fi−3fi−4= 1100010100100101000110000k⋅ fi−1fi−2fi−3fi−4fi−5
gig_igi 的计算不太好处理,等价转换以下令 GiG_iGi 表示处理前 iii 位得到的所有情况的矩阵之和,因此最后的答案就会在 GnG_nGn 中。由矩阵的乘法分配律可知 Ax+y=Ax×AyA^{x + y} = A^x \times A^yAx+y=Ax×Ay,因此可以将 GiG_iGi 进行转移。设 Ai,jA_{i,j}Ai,j 表示数字串中 [i,j][i,j][i,j] 的转移矩阵之积,则 Gi=∑j=0i−1Gj×Aj+1,iG_i = \sum \limits _{j = 0}^{i - 1} G_j \times A_{j + 1,i}Gi=j=0∑i−1Gj×Aj+1,i。
现在的复杂度为计算矩阵 Ai,jA_{i,j}Ai,j,不难发现 Ai,j=Fsi10n−i×Ai+1,jA_{i,j} = F_{s_i^{10^{n - i}}} \times A_{i + 1,j}Ai,j=Fsi10n−i×Ai+1,j,稍作变换得 Ai,j=F(10n−i)si×Ai+1,jA_{i,j} = F_{({10^{n - i}})^{s_i}} \times A_{i + 1,j}Ai,j=F(10n−i)si×Ai+1,j。这样只需要预处理出 F10iF_{10^i}F10i 的矩阵即可。
两个小细节:
-
在写矩阵时用到了
+与*的重载,这时候原来的优先级已经不复存在,因此需要通过括号来处理顺序。 -
矩阵的初始化要小心,需要避免未定义行为。
代码如下:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#define ll long long
#define ull unsigned long long
#define INF 0x3f3f3f3f
using namespace std;
const int MAX = 505;
const int MOD = 998244353;
int m,n;ll ans;
char s[MAX];
struct Mat
{
ll a[6][6];
Mat() {memset(a,0,sizeof(a));}//全部 memset 肯定没问题
Mat operator * (const Mat &y)
{
Mat z;
for (int k = 1;k <= m;++k)
for (int i = 1;i <= m;++i)
for (int j = 1;j <= m;++j)
z.a[i][j] += a[i][k] * y.a[k][j] % MOD,z.a[i][j] %= MOD;
return z;
}
Mat operator + (const Mat &y)
{
Mat z;
for (int i = 1;i <= m;++i)
for (int j = 1;j <= m;++j) z.a[i][j] = a[i][j] + y.a[i][j],z.a[i][j] %= MOD;
return z;
}
Mat qpow (Mat x,ll y)
{
Mat res;
for (int i = 1;i <= m;++i) res.a[i][i] = 1;
while (y)
{
if (y & 1) res = res * x;
x = x * x;
y >>= 1;
}
return res;
}
} p[MAX],f[MAX][MAX],g[MAX];
int main ()
{
//freopen (".in","r",stdin);
//freopen (".out","w",stdout);
scanf ("%s%d",s + 1,&m);
n = strlen (s + 1);
for (int i = 1;i <= m;++i) p[0].a[1][i] = 1;
for (int i = 2;i <= m;++i) p[0].a[i][i - 1] = 1;
for (int i = 1;i <= n;++i) p[i] = p[i].qpow (p[i - 1],10);//预处理 10^i 的矩阵
for (int j = 1;j <= n;++j)//扩展 需要注意一下 i = j 的特殊情况
for (int i = j;i;--i)//f[i][j] = f[i + 1][j] * f(10^(j - i))^(s_i)
f[i][j] = (i == j) ? f[i][j].qpow (p[0],s[i] - '0') : f[i + 1][j] * f[i][j].qpow (p[j - i],s[i] - '0');
for (int i = 1;i <= m;++i) g[0].a[i][i] = 1;
for (int i = 1;i <= n;++i)//g[i] 表示前 i 位的所有情况的答案
for (int j = 0;j < i;++j) g[i] = g[i] + (g[j] * f[j + 1][i]);//注意优先级
printf ("%lld\n",g[n].a[1][1]);//最后的答案显然是左上角的那个值
return 0;
}
【题解】数字串拆分的矩阵算法优化

被折叠的 条评论
为什么被折叠?



