[AC自动机+DP] 求有上限不带特定子串的数字数量 BZOJ3530

3530: [Sdoi2014]数数

Time Limit: 10 Sec  Memory Limit: 512 MB
Submit: 1411  Solved: 723
[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

 

 

用0 1 2 三种状态表示前 i 位 小于, 等于, 大于 上限的情况
枚举第 i + 1 位, 转移与上限的比较情况, 统计各种情况的总数

#include <bits/stdc++.h>
#define ll long long
using namespace std;

const int ml = 1210, mm = 103, mL = 1510;
const int mod = 1e9 + 7;

int num;
int nx[mL][10], fail[mL];
bool ed[mL];
void add(char a[])
{
	int len = strlen(a);
	int r = 0;
	for (int i = 0; i < len; i++)
	{
		if (nx[r][a[i] - '0'] == -1)
			nx[r][a[i] - '0'] = ++num;
		r = nx[r][a[i] - '0'];
	}
	ed[r] = 1;
}
void build_Fail()
{
	queue<int>que;
	int root = 0;
	fail[root] = root;
	for (int i = 0; i < 10; i++)
	{
		if (nx[root][i] == -1)
			nx[root][i] = root;
		else
		{
			fail[nx[root][i]] = root;
			que.push(nx[root][i]);
		}
	}
	while (!que.empty())
	{
		int now = que.front();
		que.pop();

		for (int i = 0; i < 10; i++)
		{
			if (nx[now][i] == -1)
				nx[now][i] = nx[fail[now]][i];
			else
			{
				fail[nx[now][i]] = nx[fail[now]][i];
				que.push(nx[now][i]);
			}
		}
		
		if (ed[fail[now]])
			ed[now] = 1;
	}
}

ll dp[ml][mL][3];
ll DP(char ch[])
{
	ll ans = 0;
	int len = strlen(ch);
	for (int k = 1; k < 10; k++)
	{
		if (ed[nx[0][k]])
			continue;
		
		int p;
		if (k < ch[0] - '0')
			p = 0;
		else if (k == ch[0] - '0')
			p = 1;
		else if (k > ch[0] - '0')
			p = 2;
		dp[0][nx[0][k]][p]++;
	}
	for (int j = 0; j <= num; j++)
		for (int k = 0; k < ((len > 1) ? 3 : 2); k++)
		(ans += dp[0][j][k]) %= mod;
	
	for (int i = 0; i < len - 1; i++)
	{
		for (int j = 0; j <= num; j++)
		{
			for (int k = 0; k < 10; k++) /// 枚举下一位
			{
				int p; /// 与上限的比较情况
				if (k < ch[i + 1] - '0')
					p = 0;
				else if (k == ch[i + 1] - '0')
					p = 1;
				else if (k > ch[i + 1] - '0')
					p = 2;
					
				int t = nx[j][k];
				if (ed[t])
					continue;
				(dp[i + 1][t][0] += dp[i][j][0]) %= mod;
				(dp[i + 1][t][p] += dp[i][j][1]) %= mod;
				(dp[i + 1][t][2] += dp[i][j][2]) %= mod;
			}
		}
		for (int j = 0; j <= num; j++)
		{
			for (int q = 0; q < 3; q++)
			{
				if (i + 1 == len - 1 && q == 2) /// 位数到达上限, 总值超过上限
					break;
				(ans += dp[i + 1][j][q]) %= mod;
			}
		}
	}
	return ans;
}

int main()
{
#ifndef ONLINE_JUDGE
	freopen("D:\\in.txt", "r", stdin);
#endif // ONLINE_JUDGE

	memset(nx, -1, sizeof nx);
	
	char n[ml];
	scanf("%s", n);

	int m;
	scanf("%d", &m);
	while (m--)
	{
		char ch[ml];
		scanf("%s", ch);
		add(ch);
	}
	build_Fail();

	printf("%lld\n", DP(n));
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值