又是一道神题,感觉这题的思想好强啊。
首先考虑DP,定义f[i][j]表示当前我们DP到准考证的第i位,后缀与不吉利的数字匹配了j位的方案数。
状态转移方程为f[i][j]+=f[i-1][k],可以通过枚举第i位的数字,求出k的大小,进行状态的转移。
突然发现求k的过程和KMP的失配过程差不多,于是我们可以用KMP来转移状态。
但是这个DP显然是不符合我们的要求的,时间复杂度巨大。
考虑重构DP,定义f[i][j]表示当前我们DP到不吉利数字的前i位(注意,这里和上面不一样),在第i位后放置一个0~9的数字,新的数字与不吉利数字的匹配长度为j的方案数。
突然发现我们重构的DP构造出了一个m*m的矩阵,我们是不是可以用矩阵来优化这个DP呢?
观察数据范围,m<=20,O(m^3*log2(n))的时间复杂度可以满足我们的需求。OK,这题就这样愉快的被秒掉啦。
(好迷啊,为什么大佬们直接就可以想到矩阵快速幂的?一脸懵逼)
附上AC代码:
#include <cstdio>
#include <cstring>
using namespace std;
const int N=21;
int n,m,mod,nt[N],pre,sum;
char s[N];
struct note{
int map[N][N],x,y;
inline void clear(int l,int r){return (void)(x=l,y=r,memset(map,0,sizeof map));}
inline friend note operator * (note p,note q){
note sum;sum.clear(p.x,q.y);
for (int i=0; i<=p.x; ++i)
for (int j=0; j<=q.y; ++j)
for (int k=0; k<=p.y; ++k)
sum.map[i][j]=(sum.map[i][j]+p.map[i][k]*q.map[k][j])%mod;
return sum;
}
}a,ans;
inline note power(int m){
note ret;ret.clear(n,n);
for (int i=0; i<=n; ++i) ret.map[i][i]=1;
while (m){
if (m&1) ret=ret*a;
m>>=1,a=a*a;
}
return ret;
}
int main(void){
scanf("%d%d%d",&m,&n,&mod);
scanf("%s",s+1);
for (int i=2; i<=n; ++i){
while (pre&&s[pre+1]!=s[i]) pre=nt[pre];
if (s[pre+1]==s[i]) ++pre;
nt[i]=pre;
}
a.clear(n,n);
for (int i=0; i<n; ++i)
for (int j=0; j<=9; ++j){
pre=i;
while (pre&&s[pre+1]-'0'!=j) pre=nt[pre];
if (s[pre+1]-'0'==j) ++pre;
if (pre<n) a.map[i][pre]=(a.map[i][pre]+1)%mod;
}
ans.clear(1,n),ans.map[0][0]=1,ans=ans*power(m);
for (int i=0; i<n; ++i) sum+=ans.map[0][i];
return printf("%d\n",sum%mod),0;
}