[SDOI2014]数数(AC自动机、数位dp)

链接:https://ac.nowcoder.com/acm/problem/20366
来源:牛客网
 

时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld

题目描述

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

给定N和S,计算不大于N的幸运数个数。

输入描述:

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

输出描述:

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

示例1

输入

复制20 3 2 3 14

20
3
2
3
14

输出

复制14

14

题意:

给出一个n,询问小于n的正整数中有多少数字,其十进制中不含有给出的m个串。

做法:

看起来是个很套路的题,先对m个串建一个AC自动机,然后再数位dp求解,每一层都在遍历

字典树,判断这个点极其后缀中是否含有m个串中的某一个即可,唯一坑点是给出的m个串

可能带有前导0。

比如样例

20

1

03

正确答案是20

所以定状态时需要多加一维状态表示是否有前导0。

#include <bits/stdc++.h>
#include <random>
#include <unordered_map>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

#ifdef LOCAL
#define debug(x) cout << "[" __FUNCTION__ ": " #x " = " << (x) << "]\n"
#define TIME cout << "RuningTime: " << clock() << "ms\n", 0
#else
#define TIME 0
#endif
#define hash_ 1000000009
#define Continue(x) { x; continue; }
#define Break(x) { x; break; }
const int mod = 1e9 + 7;
const int N = 1e6 + 10;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
#define gc p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1000000, stdin), p1 == p2) ? EOF : *p1++;
inline int read(){ static char buf[1000000], *p1 = buf, *p2 = buf; register int x = false; register char ch = gc; register bool sgn = false; while (ch != '-' && (ch < '0' || ch > '9')) ch = gc; if (ch == '-') sgn = true, ch = gc; while (ch >= '0'&& ch <= '9') x = (x << 1) + (x << 3) + (ch ^ 48), ch = gc; return sgn ? -x : x; }
ll fpow(ll a, int b, int mod) { ll res = 1; for (; b > 0; b >>= 1) { if (b & 1) res = res * a % mod; a = a * a % mod; } return res; }
char s[N];
char x[N];
char a[N];
int nxt[N][10];
int tot;
int sed[N];
int fail[N];
void insert(char* s)
{
	int rk = 0;
	char* x = s;
	while (*x)
	{
		int now = *x - '0';
		if (nxt[rk][now] == 0)
			nxt[rk][now] = ++tot;
		rk = nxt[rk][now];
		x++;
	}
	sed[rk]++;
}
void bulid()
{
	queue<int>q;
	for (int i = 0; i < 10; i++)
		if (nxt[0][i])
			q.push(nxt[0][i]);
	while (!q.empty())
	{
		int f = q.front();
		q.pop();
		sed[f] |= sed[fail[f]]; //可以O1查询其后缀中是否含有非法
		for (int i = 0; i < 10; i++)
			if (nxt[f][i])
				fail[nxt[f][i]] = nxt[fail[f]][i], q.push(nxt[f][i]);
			else
				nxt[f][i] = nxt[fail[f]][i];
	}
}
int d[1205][2][1510][2];
int dfs(int pos, int lim, int rk, int zero)
{
	if (pos == 0)
		return zero == 0;
	if (d[pos][lim][rk][zero] != -1)
		return d[pos][lim][rk][zero];
	int up = lim ? a[pos] : 9;
	int sum = 0;
	for (int i = 0; i <= up; i++)
	{
		int nrk = (zero && i == 0) ? 0 : nxt[rk][i];
		if (sed[nrk])
			continue;
		sum = (sum + dfs(pos - 1, i == up && lim, nrk, zero && i == 0)) % mod;
	}
	return d[pos][lim][rk][zero] = sum;
}
int main()
{
#ifdef LOCAL
	freopen("E:/input.txt", "r", stdin);
#endif
	memset(d, -1, sizeof d);
	int cnt = 0;
	scanf("%s", a + 1);
	int n = strlen(a + 1);
	reverse(a + 1, a + n + 1);
	for (int i = 1; i <= n; i++)
	{
		a[i] -= '0';
	}
	int m;
	cin >> m;
	for (int i = 1; i <= m; i++)
	{
		scanf("%s", s);
		insert(s);
	}
	bulid();
	cout << dfs(n, 1, 0, 1) << endl;
	return TIME;
}

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值