[BZOJ]3530 [SDOI2014] 数数 AC自动机 + DP

3530: [Sdoi2014]数数

Time Limit: 10 Sec   Memory Limit: 512 MB
Submit: 1226   Solved: 618
[ 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]


HOME Back

  又是这种不能包含xxx长度为yyy的字符串有多少个的题... 按照之前的做法想了个矩阵乘法的做法, 发现不仅时间要T(由于<=所以无法快速幂)而且无法处理<=n的正整数这个条件.

  那么对于这种计数题显然只能AC自动机上DP辣... 考虑<=n, n很大显然不能作为dp数组中的某一维, 那么怎么记录<=n这个条件是否满足呢? 可以考虑像数位DP那样直接记录当前是否顶位来判断. 而且关于幸运数字中的0还得和前导零区分开, 于是又得拿一维来记录当前是否是前导零状态. 那么dp数组就呼之欲出了, dp[i][j][k][t]表示当前长度为i走到j号节点是否顶位是否是前导零的字符串数. 转移比较简单.

  有几个需要注意的是刷表式过程中如果当前既是前导零状态下一个又是零的话那么只能转移到0号节点去, 而不是当前节点0号儿子去, 这点需要注意. 还有就是第一个数字是前导零状态但同时也有顶位限制(代码中!i的部分), 这个特判一下就行了.

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1505;
const int mod = 1e9 + 7;
bool vis[maxn];
char ss[maxn], str[maxn];
int n, m, tot, ans, cnt, f[maxn][maxn][2][2];
int c[maxn][11], fail[maxn];
inline void insert() {
	int p = 0;
	for (int i = 0; ss[i]; ++ i) {
		int idx = ss[i] - '0';
		if (!c[p][idx]) c[p][idx] = ++ tot;
		p = c[p][idx];
	}
	vis[p] = true;
}
queue<int> q;
inline void bfs() {
	for (int i = 0; i < 10; ++ i)
		if (c[0][i]) q.push(c[0][i]);
	while (!q.empty()) {
		int u = q.front(); q.pop();
		for (int i = 0; i < 10; ++ i) {
			int &v = c[u][i];
			if (!v) {v = c[fail[u]][i]; continue;}
			fail[v] = c[fail[u]][i], vis[v] |= vis[fail[v]];
			q.push(v);
		}
	}
}
inline void add(int &a, int b) {
	a += b;
	if (a >= mod) a -= mod;
}
inline void dp() {
	f[0][0][0][0] = 1;
	for (int i = 0; i < n; ++ i)
		for (int j = 0; j <= tot; ++ j)
			for (int k = 0; k < 2; ++ k)
				for (int t = 0; t < 2; ++ t)
					if (f[i][j][k][t]) {
						int lim = (k || !i) ? str[i] - '0' : 9;
						for (int p = 0; p <= lim; ++ p) {
							int son = c[j][p];
							if (!p && !t) add(f[i + 1][0][0][0], f[i][j][k][t]);
							if (vis[son] || (!p && !t)) continue;
							add(f[i + 1][son][(k || !i) && (p == lim)][p || t], f[i][j][k][t]);
						}
					}
	for (int i = 0; i <= tot; ++ i)
		if (!vis[i])
			for (int j = 0; j < 2; ++ j)
				add(ans, f[n][i][j][1]);
}
int main() {
	scanf("%s", str);
	n = strlen(str);
	scanf("%d", &m);
	for (int i = 0; i < m; ++ i) {
		scanf("%s", ss);
		insert();
	}
	bfs(), dp();
	printf("%d\n", ans);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值