【BZOJ3530】[Sdoi2014]数数 AC自动机+数位DP

【BZOJ3530】[Sdoi2014]数数

Description

我们称一个正整数N是幸运数,当且仅当它的十进制表示中不包含数字串集合S中任意一个元素作为其子串。例如当S=(22,333,0233)时,233是幸运数,2333、20233、3223不是幸运数。
    给定N和S,计算不大于N的幸运数个数。

Input

    输入的第一行包含整数N。
    接下来一行一个整数M,表示S中元素的数量。
    接下来M行,每行一个数字串,表示S中的一个元素。

Output

    输出一行一个整数,表示答案模109+7的值。

Sample Input

20
3
2
3
14

Sample Output

14

HINT

 下表中l表示N的长度,L表示S中所有串长度之和。

1 < =l < =1200 , 1 < =M < =100 ,1 < =L < =1500

题解:本人闲着蛋疼出了套题,考完后同学说这题PPT里有,于是看PPT发现真的有。。。于是跑来水一发~

先将所有S中的数拿出来建一个AC自动机,然后用f[i][j]表示从AC自动机上的节点i开始走j步有多少种走法。然后数位DP即可。

注意前导0的情况。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
const int mod=1000000007;
int n,m,tot;
struct node
{
	int ch[10],fail,cnt;
}p[1510];
int v[1210];
char str[1210];
int ans,f[1210][1510];
queue<int> q;
void build()
{
	int i,j,k,u,v;
	q.push(1);
	while(!q.empty())
	{
		u=q.front(),q.pop();
		for(i=0;i<=9;i++)
		{
			if(!p[u].ch[i])
			{
				if(u==1)	p[u].ch[i]=1;
				else	p[u].ch[i]=p[p[u].fail].ch[i];
				continue;
			}
			v=p[u].ch[i],q.push(v);
			if(u==1)	p[v].fail=1;
			else	p[v].fail=p[p[u].fail].ch[i],p[v].cnt|=p[p[v].fail].cnt;
		}
	}
	for(i=1;i<=tot;i++)	if(!p[i].cnt)	f[0][i]=1;
	for(j=1;j<=n;j++)	for(i=1;i<=tot;i++)	for(k=0;k<=9;k++)
		if(!p[i].cnt)	f[j][i]=(f[j][i]+f[j-1][p[i].ch[k]])%mod;
}
int main()
{
	int i,j,a,b,u;
	scanf("%s",str),n=strlen(str);
	for(i=1;i<=n;i++)	v[i]=str[n-i]-'0';
	scanf("%d",&m);
	tot=1;
	for(i=1;i<=m;i++)
	{
		scanf("%s",str),a=strlen(str);
		for(u=1,j=0;j<a;j++)
		{
			b=str[j]-'0';
			if(!p[u].ch[b])	p[u].ch[b]=++tot;
			u=p[u].ch[b];
		}
		p[u].cnt=1;
	}
	build();
	for(i=1;i<n;i++)	for(j=1;j<=9;j++)	ans=(ans+f[i-1][p[1].ch[j]])%mod;
	for(u=1,i=n;i>=1;i--)
	{
		for(j=(i==n)?1:0;j<v[i];j++)	ans=(ans+f[i-1][p[u].ch[j]])%mod;
		u=p[u].ch[v[i]];
		if(p[u].cnt)	break;
	}
	if(!p[u].cnt)	ans=(ans+1)%mod;
	printf("%d",ans);
	return 0;
}
posted @ 2017-08-30 10:35  CQzhangyu  阅读(194)  评论(0编辑  收藏  举报