P3311题解
SDNetFriend · · 题解
这是一篇记忆化搜索题解
主要谈谈这个题的特判,当时卡了我好一会
可以看出,这道题应该是数位 DP + AC 自动机
有关 AC 自动机的内容这里不再补充
非常形式化地,数位DP的状态设计:
pos :当前要处理第几位
st :用于储存前面的状态,可能是上一个数,也可能是前缀和,gcd 等等,这个题应该是前面在AC自动机上的位置
flg :当前位是否被所求数限制,比如要求的是 114514,而此时你求了前三位,现在要处理 114---,这样你第四位就只能到 5 ,但如果你第三位是 3 或者更小的,你第四位是多少就无所谓了
zero :前面是否有前导零
然后就可以愉快地开始记忆化搜索了,具体思路也不难,搜到了某一位,接着向下搜,就把新的一位在自动机上面跳一下,如果不是某个模式串的结尾就可以继续 DFS
跳完后到了最后一位只要不是全是 0 都是 1 ,因为前面各位都已经确定了
或者说解释一下 DFS 的含义,返回小于等于所求的数,所有尚未确定的可能情况的幸运数个数,因为刚开始全都不确定就会是最后的答案,到了最后全都确定了就会是 1
特判
注意这里模式串是可能有前导零的,而幸运数不允许有前导零,这可能会发生什么情况,比如你要求 114514,你处理到了第三位,是个 0023-- ,这时你的模式串有一个 0023,那么这种情况就会被判断为不合法,但实际上里应该被判断为 23-- ,不能看前导零,应该是一种合法的情况。所以总结一下,要特判的就是模式串有前导零且当前数也有前导零的情况
这里加一句特判就可以
if(zero)st=0;
然后就可以贴代码了
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <queue>
#define rint register int
#define lint long long
#define ldouble long double
using namespace std;
inline int read(){
char c;int res=0,f=1;
while(c=getchar(),!isdigit(c))if(c=='-')f*=-1;
while(isdigit(c))res=res*10+c-'0',c=getchar();
return f*res;
}
const int M=1e2+5,S=15e2+5;
const lint mod=1e9+7;
string n,s[M];
int m,len;
int nxt[S][10],fil[S],cnt=0;
bool tail[S];
inline void inst(string str){//构建字典树
int i,j,siz=str.size();
for(i=0,j=0;i<siz;++i){
int tmp=str[i]-'0';
if(!nxt[j][tmp])
nxt[j][tmp]=++cnt;
j=nxt[j][tmp];
}
tail[j]=true;
}
inline void gfil(){//获得fail指针
queue<int> que;
for(rint i=0;i<10;++i)
if(nxt[0][i]){
fil[nxt[0][i]]=0;
que.push(nxt[0][i]);
}
while(!que.empty()){
int u=que.front();
que.pop();
tail[u]|=tail[fil[u]];
for(rint i=0;i<10;++i){
if(nxt[u][i]){
fil[nxt[u][i]]=nxt[fil[u]][i];
que.push(nxt[u][i]);
}
else nxt[u][i]=nxt[fil[u]][i];
}
}
}
lint f[S][S][2][2]={0};
lint DFS(int x,int st,bool flg,bool zero){//形式化的记忆化搜索
if(x==len)return !zero;//如果不是全是0那么只有一种确定的情况
if(f[x][st][flg][zero])return f[x][st][flg][zero];
int e=(flg?n[x]-'0':9);//看看之前的位是否达到上限
lint res=0;
if(zero)st=0;
for(rint i=0;i<=e;++i){
if(!tail[nxt[st][i]]){
res+=DFS(x+1,nxt[st][i],flg&&i==e,zero&&i==0);
/*
前面顶了上限这位还顶上限那么下一位就会被限制
前面有前导零这一位还是0意味着还有前导零
*/
res%=mod;
}
}
return f[x][st][flg][zero]=res;
}
int main(){
cin>>n;
len=n.size();
m=read();
for(rint i=1;i<=m;++i){
cin>>s[i];
inst(s[i]);
}
gfil();
printf("%lld\n",DFS(0,0,true,true));
return 0;
}