分析:
题意:求给定区间内包含给定字符串的数字之和。
看到数据规模,容易想到 数位DP,并且涉及到 多模式串匹配,我们可以使用 AC自动机。
我们需要考虑 如果若干位任选,我们该怎么统计答案。 首先根据状态机类DP的套路,我们至少需要两个维度 d p i , j dp_{i, j} dpi,j 表示当前位在 j j j 号节点,还能填 i i i 个数的答案。但是这样无法与我们已经有的状态建立联系。已经有的状态是指 数位DP从前往后进行的过程中,会确定一些位上的数字,这些确定的数字构成了当前的状态。对于本题而言,已经确定的数字会影响 那些字符串已经包含 和 当前在自动机的那个节点上。所以我们新加两维,设 d p i , j , m a s k 1 , m a s k 2 dp_{i, j, mask1, mask2} dpi,j,mask1,mask2 表示当前在 j j j 号节点,还能填 i i i 个数字,当前的字符串匹配情况是 m a s k 1 mask1 mask1,目标匹配情况是 m a s k 2 mask2 mask2 的所有填数方案中 最后形成的数字之和。
考虑转移:那么就是对于当前状态,我们枚举第一个数字填什么,设填了 c c c, 那么我们让节点跳到 t r [ j ] [ c ] tr[j][c] tr[j][c], 还能填的数字还有 i − 1 i-1 i−1个,并且当前的状态为 m a s k 1 ∣ e [ t r [ j ] [ c ] ] mask1 \ | \ e[tr[j][c]] mask1 ∣ e[tr[j][c]]。 e [ t r [ j ] [ c ] ] e[tr[j][c]] e[tr[j][c]] 代表当前节点匹配的字符串的状态。
那么
d
p
i
,
j
,
m
a
s
k
1
,
m
a
s
k
2
=
∑
k
=
0
9
d
p
i
−
1
,
t
r
[
j
]
[
k
]
]
,
m
a
s
k
1
∣
e
[
t
r
[
j
]
[
k
]
]
,
m
a
s
k
2
+
n
u
m
i
−
1
,
t
r
[
j
]
[
k
]
,
m
a
s
k
1
∣
e
[
t
r
[
j
]
[
k
]
]
,
m
a
s
k
2
∗
1
0
i
−
1
∗
k
dp_{i, j, mask1, mask2} = \sum_{k = 0}^{9} dp_{i-1,tr[j][k]],mask1 \ | \ e[tr[j][k]],mask2} + num_{i-1, tr[j][k], mask1 \ | \ e[tr[j][k]], mask2} * 10^{i-1}*k
dpi,j,mask1,mask2=∑k=09dpi−1,tr[j][k]],mask1 ∣ e[tr[j][k]],mask2+numi−1,tr[j][k],mask1 ∣ e[tr[j][k]],mask2∗10i−1∗k 。
n
u
m
i
,
j
,
m
a
s
k
1
,
m
a
s
k
2
num_{i, j, mask1, mask2}
numi,j,mask1,mask2 表示当前节点位于
j
j
j 还能填
i
i
i 个数字,当前状态是
m
a
s
k
1
mask1
mask1,目标状态是
m
a
s
k
2
mask2
mask2 的填数方案数, 那么
n
u
m
num
num 的转移与
d
p
dp
dp 类似,也是枚举第一个填什么。
需要注意的是,我们要判断 m a s k 1 ∣ e [ t r [ j ] [ k ] ] mask1 \ | \ e[tr[j][k]] mask1 ∣ e[tr[j][k]] 是否为 m a s k 2 mask2 mask2 的子集。
预处理出来 d p dp dp 数组后,进行数位DP就非常简单了,我们记录当前已经确定的数字的大小 l s t lst lst, 和当前已经包含的字符串状态 m a s k mask mask,当前的节点 n o w now now,以及答案 r e s res res。每次确定一位后计算答案就行了。
需要注意的是输入会爆
l
o
n
g
l
o
n
g
long long
longlong,我们直接输入字符串,然后求
D
P
(
r
)
−
D
P
(
l
)
DP(r)-DP(l)
DP(r)−DP(l),然后暴力检验
l
l
l 是否为神数就好了。
CODE:
#include<bits/stdc++.h>// dp[i][j][mask1][mask2] 表示还能走i步,当前节点在j, 状态由mask1 变成 mask2 的数值和 可以有前导0
#define N 105
#define LL long long
#define mod 998244353
using namespace std;// num[i][j][mask1][mask2] 表示还能走i步,当前节点在j, 状态由mask1 变成 mask2 的方案数 可以有前导0
int n, tot, tr[N][10], e[N], fail[N], nn;
char str[N][N], l[N], r[N];
LL dp[N][N][1 << 5][1 << 5], num[N][N][1 << 5][1 << 5];
LL Pow(LL x, LL y){
LL res = 1, k = x % mod;
while(y){
if(y & 1) res = (res * k) % mod;
y >>= 1;
k = (k * k) % mod;
}
return res % mod;
}
void ins(int id, char *str){
int len = strlen(str + 1);
int p = 0;
for(int i = 1; i <= len; i++){
if(!tr[p][str[i] - '0']) tr[p][str[i] - '0'] = ++tot;
p = tr[p][str[i] - '0'];
}
e[p] |= (1 << (id - 1));
}
void build_ac(){
queue< int > q;
for(int i = 0; i < 10; i++)
if(tr[0][i]) q.push(tr[0][i]);
while(!q.empty()){
int u = q.front(); q.pop();
for(int i = 0; i < 10; i++){
if(tr[u][i]) fail[tr[u][i]] = tr[fail[u]][i], e[tr[u][i]] |= (e[fail[tr[u][i]]]), q.push(tr[u][i]);
else tr[u][i] = tr[fail[u]][i];
}
}
}
void pre_work(){
for(int i = 0; i <= tot; i++){
for(int j = 0; j < (1 << nn); j++){
num[0][i][j][j] = 1LL;
}
}
for(int i = 1; i <= 100; i++){//步数为阶段
for(int j = 0; j <= tot; j++){//枚举初始点
for(int mask1 = 0; mask1 < (1 << nn); mask1++){
for(int mask2 = mask1; mask2 < (1 << nn); mask2++){
if((mask2 & mask1) != mask1) continue;//结尾一定要包含自己
for(int c = 0; c < 10; c++){
int nxt = tr[j][c];
if((mask2 & e[nxt]) != e[nxt]) continue;//必须走包含的点
dp[i][j][mask1][mask2] = (((dp[i][j][mask1][mask2] + dp[i - 1][nxt][mask1 | e[nxt]][mask2])) % mod + (((num[i - 1][nxt][mask1 | e[nxt]][mask2] * Pow(10LL, 1LL * (i - 1))) % mod * (1LL * c) % mod))) % mod;
num[i][j][mask1][mask2] = (num[i][j][mask1][mask2] + num[i - 1][nxt][mask1 | e[nxt]][mask2]) % mod;
}
}
}
}
}
}
LL DP(char *str){
vector< int > nums;
int len = strlen(str + 1);
for(int i = len; i >= 1; i--) nums.push_back(str[i] - '0');
LL res = 0, lst = 0;
int mask = 0, now = 0;
for(int i = nums.size() - 1; i >= 0; i--){//规定第一位不能为0
int x = nums[i];
if(i == nums.size() - 1){//第一位
for(int j = 1; j < x; j++){//填j
int tc = tr[now][j], nmask = (mask | e[tc]);
LL tnum = (lst * 10LL + j);
res = (res + ((((tnum * Pow(10LL, 1LL * i)) % mod) * num[i][tc][nmask][(1 << nn) - 1]) % mod) + dp[i][tc][nmask][(1 << nn) - 1]) % mod;
}
}
else{
for(int j = 0; j < x; j++){//填j
int tc = tr[now][j], nmask = (mask | e[tc]);
LL tnum = (lst * 10LL + j);
res = (res + ((((tnum * Pow(10LL, 1LL * i)) % mod) * num[i][tc][nmask][(1 << nn) - 1]) % mod) + dp[i][tc][nmask][(1 << nn) - 1]) % mod;
}
}
now = tr[now][x];
mask |= e[now];
lst = (lst * 10LL + x) % mod;
if(i == 0 && (mask == ((1 << nn) - 1))) res = (res + lst) % mod;
}
for(int i = nums.size() - 1; i >= 1; i--){//枚举最高位
for(int j = 1; j <= 9; j++){
int c = tr[0][j], mask = e[tr[0][j]];
res = (res + ((((Pow(10LL, 1LL * (i - 1)) * (1LL * j)) % mod) * num[i - 1][c][mask][(1 << nn) - 1]) % mod) + dp[i - 1][c][mask][(1 << nn) - 1]) % mod;
}
}
return res;
}
LL check(char *S){
LL num = 0, f = 1; int len = strlen(S + 1);
for(int i = 1; i <= len; i++) num = (num * 10LL + (S[i] - '0')) % mod;
for(int i = 1; i <= nn; i++){
int tl = strlen(str[i] + 1);
bool ff = 0;
for(int j = 1; j <= len; j++){
if(len - j + 1 < tl) break;
if(S[j] == str[i][1]){
bool fff = 1;
for(int k = 1; k < tl; k++){
if(S[j + k] != str[i][k + 1]){
fff = 0;
break;
}
}
if(fff){
ff = 1;
break;
}
}
}
if(!ff) f = 0;
}
return num * f;
}
int main(){
freopen("d.in", "r", stdin);
freopen("d.out", "w", stdout);
scanf("%d%s%s", &nn, l + 1, r + 1);
for(int i = 1; i <= nn; i++){
scanf("%s", str[i] + 1);
ins(i, str[i]);
}
build_ac();
pre_work();
cout << (((DP(r) - DP(l)) % mod + mod) % mod + check(l)) % mod<< endl;
return 0;
}