题目大意
求不大于 N 的正整数中,看作字符串(不包含前缀0)后,没有子串属于给定字符串集 S 的数的个数。
1≤N<101200,|S|≤100,∑s∈S|s|≤1500
题目分析
不大于某个数,然后对于数字的某些位有特殊要求,这是经典的数位dp模型。
那如何解决子串的约束条件呢?可以发现约束条件相当于多串匹配。那我们考虑就 S 集合建立AC自动机,然后通过确定当前状态在自动机内的指针,来确定状态是否合法。
在建立自动机,处理 fail 数组时,我们同时处理布尔数组 markx ,表示节点 x 及其fail指针指向节点(可以指多次)中是否有标记为单词的。
我们从高位往低位(字符串前往后)枚举,设 fi,j,0..1 表示枚举到第 i 位,当前状态在自动机内的j节点,当前组成的数小于(等于) N 的方案数。
我们枚举i的合法状态向 i+1 转移,状态合法当且仅当 markj 为 false 。 fi,j,0 可以转移到 fi+1,nextj,k,0(k∈[0,9]) , fi,j,1 可以转移到 fi+1,nextj,Ni+1,1 和 fi+1,nextj,k,0(k∈[0,Ni+1)) 。其中 Ni 表示 N 看成字符串后第i位的数值。
注意不能有前导 0 ,但是仍需考虑不足数位的情况。所以我们还可以在每次i转移到 i+1 时,顺便转移 fi+1,next[root][k],0 ,也就是这一位开始有大于 0 的数。
设N共有 n 位,自动机节点数为tot,最后答案固然为 ∑i<=toti=0fn,i,0+fn,i,1(marki=false) 。
时间复杂度即为 O(n×tot) 。
代码实现
作者闲得蛋疼,开了滚动数组,空间大跳楼。
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
using namespace std;
const int P=;
const int N=;
const int L=;
int f[][L+][];
queue<int> Q;
char n[N+];
int d,ans,m;
const int W=;
char str[L+];
struct AC_automation
{
int fail[L+],next[L+][W];
bool mark[L+];
int tot,root;
int newnode()
{
fail[tot]=-;
for (int i=;i<W;i++)
next[tot][i]=-;
mark[tot]=false;
return tot++;
}
void init()
{
tot=;
root=newnode();
}
void insert()
{
int l=strlen(str),rt=root;
for (int i=;i<l;i++)
if (next[rt][str[i]-'0']!=-)
rt=next[rt][str[i]-'0'];
else
{
next[rt][str[i]-'0']=newnode();
rt=next[rt][str[i]-'0'];
}
mark[rt]=true;
}
void build()
{
for (int i=;i<W;i++)
if (next[root][i]==-)
next[root][i]=root;
else
{
fail[next[root][i]]=root;
Q.push(next[root][i]);
}
while (!Q.empty())
{
int rt=Q.front();
Q.pop();
mark[rt]|=mark[fail[rt]];
for (int i=;i<W;i++)
if (next[rt][i]==-)
next[rt][i]=next[fail[rt]][i];
else
{
fail[next[rt][i]]=next[fail[rt]][i];
Q.push(next[rt][i]);
}
}
}
void dp()
{
int now=,tsf=;
for (int i=;i<n[]-'0';i++)
f[tsf][next[root][i]][]++;
f[tsf][next[root][n[]-'0']][]++;
for (int i=;i<d-;i++)
{
now^=,tsf^=;
memset(f[tsf],,sizeof f[tsf]);
for (int j=;j<W;j++)
f[tsf][next[root][j]][]++;
for (int j=;j<tot;j++)
if (!mark[j])
{
(f[tsf][next[j][n[i+]-'0']][]+=f[now][j][])%=P;
for (int k=;k<n[i+]-'0';k++)
(f[tsf][next[j][k]][]+=f[now][j][])%=P;
for (int k=;k<W;k++)
(f[tsf][next[j][k]][]+=f[now][j][])%=P;
}
}
ans=;
for (int i=;i<tot;i++)
if (!mark[i])
(((ans+=f[tsf][i][])%=P)+=f[tsf][i][])%=P;
}
}atm;
int main()
{
freopen("count.in","r",stdin);
freopen("count.out","w",stdout);
scanf("%s",n);
d=strlen(n);
atm.init();
scanf("%d",&m);
for (int i=;i<=m;i++)
{
scanf("%s",str);
atm.insert();
}
atm.build();
atm.dp();
printf("%d\n",ans);
fclose(stdin);
fclose(stdout);
}