洛谷 P3311 [SDOI2014] 数数

Description

洛谷传送门

Solution

看起来像是一道数位 (dp)

但是怎么做呢?我们可以联想到文本生成器那道题。

这道题的思路也差不太多,就是在 (AC) 自动机上跑 (dp)

那么如何判断一个数是否出现过集合 (s) 中的字符串呢?

这个也很简单,我们建 (trie) 时先标记一下每个字符串最后结尾的位置,建 (fail) 指针时,考虑到 (fail) 指向的位置是最长前缀,所以

End[x] |= End[fail[x]]

记忆化搜索时,在 (trie) 上跑,向下搜。

具体看代码吧。

Code

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#define ll long long

using namespace std;

const ll mod = 1e9 + 7;
const ll N = 1600;
char n[N], s[N];
ll m;
ll trie[N][27], tot;
ll End[N], fail[N];
ll dp[N][N][2][2];

inline void insert(char s[]){
	ll now = 0, len = strlen(s);
	for(ll i = 0; i < len; i++){
		ll c = s[i] - '0';
		if(!trie[now][c]) trie[now][c] = ++tot;
		now = trie[now][c];
	}
	End[now] = 1;
}

inline void build(){
	queue <ll> q;
	for(ll i = 0; i < 10; i++)
		if(trie[0][i]) q.push(trie[0][i]);
	while(!q.empty()){
		ll now = q.front();
		q.pop();
		for(ll i = 0; i < 10; i++){
			ll nxt = trie[now][i];
			if(nxt){
				fail[nxt] = trie[fail[now]][i];
				End[nxt] |= End[fail[nxt]];
				q.push(nxt);
			}else trie[now][i] = trie[fail[now]][i];
		}
	}
}

inline ll dfs(ll len, ll now, ll flag, ll lim){
	if(len <= 0) return !End[now];
	if(End[now]) return 0;
	if(dp[len][now][flag][lim] != -1) return dp[len][now][flag][lim];
	ll x = lim ? n[len] - '0' : 9;
	ll res = 0;
	for(ll i = 0; i <= x; i++)
		res = (res + dfs(len - 1, (flag && (!i)) ? 0 : trie[now][i], flag && (!i), lim && (n[len] - '0' == i)) + mod) % mod;
	return dp[len][now][flag][lim] = res;
}

signed main(){
	scanf("%s%lld", n + 1, &m);
	for(ll i = 1; i <= m; i++){
		scanf("%s", s);
		insert(s);
	}
	build();
	ll len = strlen(n + 1);
	for(ll i = 1; i <= len / 2; i++)
		swap(n[i], n[len - i + 1]);
	memset(dp, -1, sizeof(dp));
	printf("%lld
", (dfs(len, 0, 1, 1) + mod - 1) % mod);
	return 0;
}

End

本文来自博客园,作者:xixike,转载请注明原文链接:https://www.cnblogs.com/xixike/p/15374648.html

原文地址:https://www.cnblogs.com/xixike/p/15374648.html