题解 P3311 【[SDOI2014]数数】

ωαηg

2019-05-04 08:29:54

Solution

看到题解里面都是递推式dp,我来补一发记忆化搜索式的 ---- ### 主要思路 楼上已经讲得很清楚了。但~~为了过审~~为了加深理解,我还是来~~扯~~略讲一下。 看到题目里的多模式串匹配,显然可以想到AC自动机。再看到“计算不大于N的幸运数个数”,我们又可以发现这与数位dp有关。所以这题的算法正是:AC自动机+数位dp。 首先是AC自动机的基本操作,把集合S读进来,建立trie树,求出前缀指针。 然后,我们的问题就转化为了:在加上前缀指针的trie树上,找一条路径,不经过任何结尾节点,并且一路上经过的边所组成的数字不大于$n$,有多少种方案。 数位dp实现。 注意,一个节点是结尾节点,当且仅当这个点本身是结尾节点,或者其失配节点是结尾节点,或者其失配节点的失配节点是结尾节点,或者其失配节点的失配节点的。。。是结尾节点。我么可以用AC自动机求完前缀指针之后队列里面的残留数据来实现这一点。(具体见代码) ---- ### 代码实现 AC自动机基本操作不再多讲,不会请自行补习。 这里重点讲一下数位dp。 我们设$f[i][j]$表示当前在处理第i位,当前在trie树的j这个节点,并且保证当前构成的数严格小于n,接下去有多少个符合要求的数字。 显然,在转移的时候,我们可以暴力枚举接下来选哪一个数。假设接下去选的数是k: $f[i][j]=\sum f[i+1][ch[j][k]](ch[j][k]$ 不是结尾节点$)$ 我们可以用记忆化搜索实现这一转移,代码如下:(我的数位dp中大小限制变量与别人是反过来的,阅读可能引起不适,请谅解): ```cpp int dfs(int pos,int ch_pos,bool down,bool qiandaoling){ if(pos>lenn) return !qiandaoling;//注意,请处理前导零问题 if(down && ~f[pos][ch_pos]) return f[pos][ch_pos];//~f[pos][ch_pos]是f[pos][ch_pos]!=-1的装逼写法 int end=down?9:sn[pos]-'0',res=0;//阅读可能引起不适,请谅解。 for(int i=0;i<=end;i++){ if(qiandaoling){//处理前导零问题 if(!bo[ch[1][i]]) res=(res+dfs(pos+1,ch[1][i],down || i!=end,qiandaoling && i==0))%p;//转移 } else{ if(!bo[ch[ch_pos][i]]) res=(res+dfs(pos+1,ch[ch_pos][i],down || i!=end,false))%p;//转移 } } if(down && !qiandaoling) f[pos][ch_pos]=res;//记忆化 return res; } ``` 可以看到,我们刚刚处理了前导零问题。那么为什么要处理前导零呢?因为此题数据太坑爹。 我们可以看看这个数据: > 10 > 1 > 01 如果你不处理前导零,那么你的输出将是9,而正确输出则是10. 因为在数位dp的时,我们在构成数字1的过程中一不小心包含了“01”这个子串(因为我们的n有两位,所以是从十位开始数位dp的)。而事实上,数字1是不包含"01"这个子串的,因为那个0是前导零。 所以我们要特判一下,忽略前导零。 ---- ### 真 · 代码 ```cpp #include<bits/stdc++.h> #define int long long #define next fuck using namespace std; int const max_ch=1505; int const p=1e9+7; int m,lenn,tot=1,ch[max_ch][10],next[max_ch],que[max_ch],f[1205][max_ch]; bool bo[max_ch]; char sn[1205],s[1505]; inline void insert(char *s){ int u=1,len=strlen(s); for(int i=0;i<len;i++){ int c=s[i]-'0'; if(!ch[u][c]) ch[u][c]=++tot; u=ch[u][c]; } bo[u]=true; } inline void get_next(){ for(int i=0;i<10;i++) ch[0][i]=1; next[1]=0,que[1]=1; for(int l=1,r=1;l<=r;l++){ int u=que[l]; for(int i=0;i<10;i++){ if(!ch[u][i]) ch[u][i]=ch[next[u]][i]; else{ que[++r]=ch[u][i]; next[ch[u][i]]=ch[next[u]][i]; } } } } int dfs(int pos,int ch_pos,bool down,bool qiandaoling){ if(pos>lenn) return !qiandaoling; if(down && ~f[pos][ch_pos]) return f[pos][ch_pos]; int end=down?9:sn[pos]-'0',res=0; for(int i=0;i<=end;i++){ if(qiandaoling){ if(!bo[ch[1][i]]) res=(res+dfs(pos+1,ch[1][i],down || i!=end,qiandaoling && i==0))%p; } else{ if(!bo[ch[ch_pos][i]]) res=(res+dfs(pos+1,ch[ch_pos][i],down || i!=end,false))%p; } } if(down && !qiandaoling) f[pos][ch_pos]=res; return res; } signed main(){ scanf("%s",sn+1); lenn=strlen(sn+1); scanf("%lld",&m); for(int i=1;i<=m;i++){ scanf("%s",s); insert(s); } get_next(); for(int i=1;i<=tot;i++) bo[que[i]]=bo[que[i]] || bo[next[que[i]]]; //利用队列里的残留数据 memset(f,-1,sizeof(f)); printf("%lld\n",dfs(1,1,false,true)); return 0; } ```