分析:
第一次写KMP的题目(就这么难……….我好可怜O(≧口≦)O)
今天秦神上课讲了这道题,没听懂,不会写QAQ,而且本来也不会写KMP,所以一上午都耗在这道题上了…………
首先来说一说KMP算法的nxt数组,nxt[i]代表前i位最长前缀等于后缀的长度,我们先假装你会求nxt数组,我们先来研究一下nxt是、指针有什么用?
在研究nxt指针之前我们先来研究一个好玩的小人漫步游戏(其实就是AC自动机)
我们有这样一个字符串abaaba
它的AC自动机长什么样子捏?_?
长这个样子,小人从0号节点出发,首先如果我们匹配到第一个字符为a那么小人就走到1号节点,否则就不动,到达1号节点之后,如果我们很幸运地碰到了b,那么小人就可应走到2号节点,但是如果我们碰到a怎么办呢,答案是原地不动,因为我们现在面临的串是aa,但是很明显我们的匹配串并没有aa这个前缀,但是我们有a这个前缀,所以我们面临的串可以看为*a,so原地踏步就好了,但是如果碰到其他字符就真的over了,只能回到0号节点,乖乖地接着匹配。
然后假如小人碰到了b到了2号节点,然后很幸运地又碰到了a到了3号节点,接下来就很不幸没有碰到a,碰到了b,怎么办呢?回到原点吗?不,应该回到2号节点,为什么呢??小人当前面临的串为abab,我们没有abab这个前缀,但是我们有ab这个前缀呀,所以串可以看成**ab那不就是2号节点的状态吗,其他指针与此原理相同,就不再赘述。通过张图我们可以发现,其实小人每次匹配失败之后,跳转到的地方都是当前失败状态的一个后缀的开始,而这个后缀与前缀相同……….感觉很有道理
接下来再说nxt指针
nxt指针其实也可以形象地成为失败指针,也就是说当模式串与匹配串匹配失败之后,需要借助nxt指针来跳转到下一个开始重新匹配的地方,而不用每次向后以龟速爬行一步(……..)
画张图来解释以下(我是个灵魂画师,大家不要介意)
当我们匹配到蓝色部分完成时候发现下一个字符无法匹配,暴力的思想应该是把匹配串向后挪一个字符接着匹配,但是我们观察一下就可以发现,其实和AC自动机一样,当前匹配失败后,我们可以直接跳转到j处开始匹配,原理和AC自动机相同……………>o<
接下来就要面临一个很严肃的问题,怎么求nxt指针(⊙o⊙)…
看了很久其他人的文章我依旧是十脸茫然……………..>_<
后来抄了几遍代码忽然就懂了…………
但是我还是不知道怎么讲出来………(觉得自己弱爆了),可能理解还不够深刻吧,过几天再看看或许会有新的收获,所以在这里只放一个链接,防止自己以后忘光了不知道看啥………….
nxt指针的计算方法
所以接下来我们回归这道题:
其实这道题可以转化为从初始状态,到达非结尾状态的路径有多少条(初始状态就是0,结尾状态就是不吉利数字完全匹配)
f[i][j]代表从i状态到达j状态经过一条路径的方案数,根据POJ3613的经验我们可以知道,f^n代表经过n条边的方案数,经过n条边什么意思呢,就是填n个数字
所以问题就转化为了如何构造初始矩阵
根本不会……………后来经过YOUSIKI童鞋的讲解,还有结合秦神的代码才勉强把这道题A了QAQ忧伤ing………..
首先我们我们知道每一位数字只有10种可能的数字0~9,所以如果当前处于i状态,我们就枚举我们要通过哪条路径到达下一个状态,也就是下一位填哪个数字,并且不吉利数字的第i+1位等于我们当前枚举的j,那么就代表从i可以转移为i+1,所以M.a[i][i+1]++,代表多了一种方案数,如果不是,就代表匹配失败,我们就要通过nxt指针寻找下一个开始重新匹配的地方k,也就是说i可以转移为k所以M.a[i][k]++
大概就是这个意思吧,说的不够清楚,但愿将来我再看的时候还看得懂QAQ……….>_<
代码如下:
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int maxn=20+5;
int n,m,MOD,str[maxn],nxt[maxn];
char s[maxn];
struct lala{
int a[maxn][maxn];
}ONE,S,RES,M;
lala operator *(lala x,lala y){
lala ans;
memset(ans.a,0,sizeof(ans.a));//一定要记得初始化
for(int i=0;i<m;i++)
for(int j=0;j<m;j++)
for(int k=0;k<m;k++)
ans.a[i][j]=(ans.a[i][j]+x.a[i][k]*y.a[k][j]%MOD)%MOD;
return ans;
}
inline void getnext(){
int k;
nxt[0]=nxt[1]=0;
for(int i=1;i<m;i++){
k=nxt[i];
while(k&&str[k+1]!=str[i+1])
k=nxt[k];
if(str[k+1]==str[i+1])
nxt[i+1]=k+1;
else
nxt[i+1]=0;
}
}
inline lala pow(lala a,int b){
lala ans=ONE;
while(b){
if(b&1)
ans=ans*a;
a=a*a,b>>=1;
}
return ans;
}
inline void getmatrix(){
for(int i=0;i<m;i++)
for(int j=0;j<=9;j++)
M.a[i][j]=0;//一定要记得初始化
for(int i=0;i<m;i++)
for(int j=0;j<=9;j++){
int k=i,pos;
while(k&&str[k+1]!=j)
k=nxt[k];
if(str[k+1]==j)
pos=k+1;
else
pos=0;
M.a[i][pos]++;
}
}
signed main(void){
scanf("%d%d%d",&n,&m,&MOD);
scanf("%s",s+1);
for(int i=1;i<=m;i++)
str[i]=s[i]-'0';
getnext();
for(int i=0;i<=m;i++)
ONE.a[i][i]=1;
getmatrix();
RES=pow(M,n);
int ans=0;
for(int i=0;i<m;i++)
ans=(ans+RES.a[0][i])%MOD;
cout<<ans<<endl;
return 0;
}
这道题有另一个非算法的收获–一定要记得初始化矩阵,今天没有意识到初始化,怎么调都过不了样例(我因为这个被嘲笑了O(≧口≦)O)
by >o< neighthorn