bzoj 3530: [Sdoi2014]数数 (AC自动机+数位DP)

3530: [Sdoi2014]数数

Time Limit: 10 Sec   Memory Limit: 512 MB
Submit: 814   Solved: 423
[ Submit][ Status][ Discuss]

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

Source

[ Submit][ Status][ Discuss]

题解:AC自动机+数位DP

对于S中的元素,建立AC自动机,打isend标记的时候注意如果该点fail指针指向的节点有isend标记,那么这个点也应该有isend标记。

然后进行数位dp,f[i][j][0/1][0/1]表示是第i位匹配到AC自动机的第j个节点是否卡上下界的方案数。

转移的时候注意不要转移到有isend标记的节点上。

因为这道题我们统计的数是不包括前导零的,所以需要特判,如果之前一直是0,那么我们就让匹配的节点一直停在根节点。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#define N 2300
#define p 1000000007
#define LL long long
using namespace std;
int ch[N*10][13],isend[N*10],a[N],n,m,cnt,b[N],fail[N*10];
char s[N],s1[N];
LL f[N][N][2][2];
void insert(char s[])
{
	int len=strlen(s+1); int now=0;
	for (int i=1;i<=len;i++) {
		int x=s[i]-'0';
		if (ch[now][x]) now=ch[now][x];
		else ch[now][x]=++cnt,now=cnt;
	}
	isend[now]=1;
}
void makefail()
{
	int now=0;
	queue<int> q;
	for (int i=0;i<=9;i++)
	 if (ch[now][i]) q.push(ch[now][i]),fail[ch[now][i]]=0;
	while (!q.empty()) {
		int now=q.front(); q.pop();
		for (int i=0;i<=9;i++){
			if (!ch[now][i]) {
				ch[now][i]=ch[fail[now]][i];
				continue;
			}
			int t=fail[now];
			fail[ch[now][i]]=ch[t][i];
			if (isend[ch[t][i]]) isend[ch[now][i]]=1;
			q.push(ch[now][i]);
		}
	}
}
int main()
{
	freopen("A.in","r",stdin);
	freopen("A.out","w",stdout);
	scanf("%s",s+1);
	n=strlen(s+1);
	for (int i=1;i<=n;i++) a[i]=s[i]-'0'; 
	scanf("%d",&m);
	for (int i=1;i<=m;i++) {
		scanf("%s",s1+1);
		insert(s1);
	}
	int now=0;
	for (int i=0;i<=9;i++)
	 if (!ch[now][i]) ch[now][i]=++cnt;
	makefail();
	for (int i=1;i<=n;i++) b[i]=0; b[n]=1;
	//for (int i=1;i<=n;i++) cout<<a[i];
	//cout<<endl;
	//for (int i=1;i<=n;i++) cout<<b[i];
	//cout<<endl;
    memset(f,0,sizeof(f));
    if (n!=1) f[1][now][1][0]++;
	for (int i=1;i<=9;i++) 
	if (!isend[ch[now][i]])
	 {
	 	if (i<b[1]||i>a[1]) continue;
	 	if (i>b[1]&&i<a[1]) f[1][ch[now][i]][0][0]++;
	 	else if (i==b[1]&&i==a[1]) f[1][ch[now][i]][1][1]++;
	 	else if (i==b[1]) f[1][ch[now][i]][1][0]++;
	 	else if (i==a[1]) f[1][ch[now][i]][0][1]++;
	 }
	for (int i=1;i<n;i++) 
	 for (int j=0;j<=cnt;j++)
	  for (int a1=0;a1<=1;a1++)
	   for (int b1=0;b1<=1;b1++)
	    if (f[i][j][a1][b1]) {
	    	//cout<<i<<" "<<j<<" "<<a1<<" "<<b1<<" "<<f[i][j][a1][b1]<<endl;
	    	for (int x=0;x<=9;x++)
	    	{
	    	 if (b1&&x>a[i+1]||a1&&x<b[i+1]) continue;
	    	 if (ch[j][x]) {
	    	 	int t=ch[j][x];
	    	 	if (a1&&x==0) t=now;
	    	 	if (!isend[t]) {
	    	 		int pd=0; int pd1=0;
	    	 		if (b1&&a[i+1]==x) pd=1;
	    	 		if (a1&&b[i+1]==x) pd1=1;
	    	 		f[i+1][t][pd1][pd]=(f[i+1][t][pd1][pd]+f[i][j][a1][b1])%p;
				 }  
			 }
			 else {
			 	int t=ch[0][x];
			 	if (a1&&x==0) t=now;
			 	if (!isend[t]) {
			 		int pd=0; int pd1=0;
	    	 		if (b1&&a[i+1]==x) pd=1;
	    	 		if (a1&&b[i+1]==x) pd1=1;
	    	 		f[i+1][t][pd1][pd]=(f[i+1][t][pd1][pd]+f[i][j][a1][b1])%p;
				 } 
			 }
		    }
		}
	LL ans=0;
	for (int i=0;i<=cnt;i++)
	 for (int a1=0;a1<=1;a1++)
	  for (int b1=0;b1<=1;b1++)
	   ans=(ans+f[n][i][a1][b1])%p;
	printf("%lld\n",ans);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值