BZOJ3530 SDOI2014 数数
题目大意
给定一个长度为$l$的数$n(l\leq 1200)$,给定$m$个可能包含前导$0$的数字串$m\leq 100$,求有多少个不超过$n$的的数在去前导零后不包含任何一个子串数字串。
题解
很裸的$AC$自动机$+$数位$Dp$题。
对于$m$个数字串建$AC$自动机,$ F_{i,j,k=\{0,1\}}$表示前$i$位匹配到$AC$自动机上的第$j$个节点,有$(k=1)$无$(k=0)$卡到数的上界的方案数,遇到结尾数字串的时候不进行转移即可。关于$n$前导$0$不能对匹配产生影响的问题,我们不能使用传统的令第最高$+1$位卡上界的初始状态答案为$1$,而是要枚举$n$的最高位在哪一位,枚举最高为是几,把这些位在$AC$自动机上的初始状态的$DP$值设为$1$,然后照常做即可。
然后就是写$AC$自动机的时候一定要把$x$的标记从$fail_x$处转移过来,不然会出事情,比如$BZOJ$讨论群中的
1234
2
121
2
正确代码输出:890
错误代码输出:930
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<cmath> #define LL long long #define M 1510 #define mod 1000000007 using namespace std; int read(){ int nm=0,fh=1; char cw=getchar(); for(;!isdigit(cw);cw=getchar()) if(cw=='-') fh=-fh; for(;isdigit(cw);cw=getchar()) nm=nm*10+(cw-'0'); return nm*fh; } int n,m,fd[M],t[M][10],q[M<<1],hd,tl,rt,cnt,F[M][M][2],ans; void upd(int &x,int y){x=((x+y>=mod)?x+y-mod:x+y);} char ch[M],s[M]; bool vis[M]; void ins(int &x,char *k,int rem){ if(!x) x=++cnt; if(!rem){vis[x]=true;return;} ins(t[x][*k-'0'],k+1,rem-1); } int main(){ scanf("%s",ch+1),n=strlen(ch+1); for(int T=read();T;--T) scanf("%s",s),ins(rt,s,strlen(s)); for(int i=0;i<10;i++) if(t[rt][i]) fd[t[rt][i]]=1,q[tl++]=t[rt][i]; else t[rt][i]=1; for(fd[rt]=rt;hd<tl;){ int x=q[hd++]; vis[x]|=vis[fd[x]]; for(int k=0;k<10;k++){ if(!t[x][k]) t[x][k]=t[fd[x]][k]; else fd[t[x][k]]=t[fd[x]][k],q[tl++]=t[x][k]; } } for(int i=1;i<=ch[1]-'0';i++) if(!vis[t[rt][i]]) F[1][t[rt][i]][i==(ch[1]-'0')]++; for(int i=2;i<=n;i++) for(int j=1;j<10;j++) if(!vis[t[rt][j]]) F[i][t[rt][j]][0]++; for(int i=1;i<=n;i++){ for(int k=1;k<=cnt;k++){ if(vis[k]){F[i][k][0]=F[i][k][1]=0;continue;} if(i==n){upd(ans,F[i][k][0]),upd(ans,F[i][k][1]);continue;} if(F[i][k][0]) for(int D=0;D<10;D++) upd(F[i+1][t[k][D]][0],F[i][k][0]); if(F[i][k][1]) for(int D=0;D<=ch[i+1]-'0';D++) upd(F[i+1][t[k][D]][D+'0'==ch[i+1]],F[i][k][1]); } } printf("%d\n",ans); return 0; }