模拟赛好题--godnumber 题解

在这里插入图片描述

分析:
          题意:求给定区间内包含给定字符串的数字之和。

          看到数据规模,容易想到 数位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 i1个,并且当前的状态为 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=09dpi1,tr[j][k]],mask1  e[tr[j][k]],mask2+numi1,tr[j][k],mask1  e[tr[j][k]],mask210i1k
          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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值