【bzoj3530】[Sdoi2014]数数 AC自动机+数位dp

题目描述

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

输入

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

输出

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

样例输入

20
3
2
3
14

样例输出

14



题解

AC自动机+数位dp

同学的某道考试题的加强版。。。

由于给定的串多且复杂,要求不能匹配到这些串,所以可以对这些串建立Trie图。

然后设$f[i][j]$表示从$j$开始走$i$个节点,不触碰到危险节点(即得到的是非幸运数)的方案数。

那么如果$j$是危险节点则方案数为0,否则枚举$j$走出去的第一步,用$f[i-1][next[j][k]]$更新$f[i][j]$。

考虑数位dp的过程:

首先处理出位数不满$n$位的数的个数:枚举位数和最高位(非0),然后方案数即为$f[i][next[1][j]]$。

然后考虑位数满$n$位的数的个数:

考虑从高到底的每一位:从0到当前位-1是满的(即后面的数恰好从0到10^{位数}),因此可以直接从$f$中取出。再考虑当前位不满的情况,此时转化为了子问题,按照同样的方法处理即可。

注意最高位是不能包含前导0的,而确定最高位以后其余的位是可以包含前导0的。

时间复杂度$O(10nL)$

细节贼多。。。代码凑合着看吧。。。

#include <queue>
#include <cstdio>
#include <cstring>
#define N 1510
#define mod 1000000007
using namespace std;
queue<int> q;
int next[N][10] , tot = 1 , fail[N] , tag[N] , f[N][N];
char s[N] , w[N];
void build()
{
	int x , i;
	for(i = 0 ; i < 10 ; i ++ ) next[0][i] = 1;
	q.push(1);
	while(!q.empty())
	{
		x = q.front() , q.pop() , tag[x] |= tag[fail[x]];
		for(i = 0 ; i < 10 ; i ++ )
		{
			if(next[x][i]) fail[next[x][i]] = next[fail[x]][i] , q.push(next[x][i]);
			else next[x][i] = next[fail[x]][i];
		}
	}
}
int main()
{
	int n , m , i , j , k , t , flag , ans = 0;
	scanf("%s%d" , s , &m) , n = strlen(s);
	for(i = 1 ; i <= m ; i ++ )
	{
		scanf("%s" , w);
		for(j = 0 , t = 1 ; w[j] ; j ++ )
		{
			if(!next[t][w[j] ^ ‘0‘]) next[t][w[j] ^ ‘0‘] = ++tot;
			t = next[t][w[j] ^ ‘0‘];
		}
		tag[t] = 1;
	}
	build();
	for(i = 1 ; i <= tot ; i ++ ) f[1][i] = !tag[i];
	for(i = 2 ; i <= n ; i ++ )
		for(j = 1 ; j <= tot ; j ++ )
			if(!tag[j])
				for(k = 0 ; k < 10 ; k ++ )
					f[i][j] = (f[i][j] + f[i - 1][next[j][k]]) % mod;
	for(i = 1 ; i < n ; i ++ )
		for(j = 1 ; j < 10 ; j ++ )
			ans = (ans + f[i][next[1][j]]) % mod;
	for(i = 0 , t = flag = 1 ; i < n ; i ++ )
	{
		for(j = flag ; j < (s[i] ^ ‘0‘) ; j ++ ) ans = (ans + f[n - i][next[t][j]]) % mod;
		t = next[t][s[i] ^ ‘0‘];
		if(tag[t]) break;
		flag = 0;
	}
	if(!tag[t]) ans = (ans + 1) % mod;
	printf("%d\n" , ans);
	return 0;
}
时间: 2024-11-07 14:54:15

【bzoj3530】[Sdoi2014]数数 AC自动机+数位dp的相关文章

HDU 4518 ac自动机+数位dp

吉哥系列故事--最终数 Time Limit: 500/200 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others) Total Submission(s): 304    Accepted Submission(s): 102 Problem Description 在2012年腾讯编程马拉松比赛中,吉哥解决了一道关于斐波那契的题目,这让他非常高兴,也更加燃起了它对数学特别是斐波那契数的热爱.现在,它又在思考一个关于斐波那契

zoj 3494 BCD Code(AC自动机+数位dp)

题目链接:zoj 3494 BCD Code 题目大意:给定n个2进制串,然后有一个区间l,r,问说l,r之间有多少个数转换成BCD二进制后不包含上面的2进制串. 解题思路:AC自动机+数位dp.先对禁止串建立AC自动机,所有的单词节点即为禁止通行的节点.接着进行数位dp, 用solve(r) - solve(l-1), 这里的l需要用到大数减法.dp[i][j]表示第i为移动到节点j的可行方案数,每次枚举下一位数字,因 为是BCD二进制,所以每位数要一次性移动4个字符,中途有经过禁止点都是不行

zoj3494BCD Code(ac自动机+数位dp)

l链接 这题想了好一会呢..刚开始想错了,以为用自动机预处理出k长度可以包含的合法的数的个数,然后再数位dp一下就行了,写到一半发现不对,还要处理当前走的时候是不是为合法的,这一点无法移到trie树上去判断. 之后想到应该在trie树上进行数位dp,走到第i个节点且长度为j的状态是确定的,所以可以根据trie树上的节点来进行确定状态. dp[i][j]表示当前节点为i,数第j位时可以包含多少个合法的数. 1 #include <iostream> 2 #include<cstdio>

[AC自动机+概率dp] hdu 3689 Infinite monkey theorem

题意: 给n个字母,和m次数. 然后输入n个字母出现的概率 然后再给一个目标串str 然后问m次中敲出目标串的概率是多少. 思路: AC自动机+概率dp的简单题. 首先建立trie图,然后就是状态转移了 dp版本: dp三重循环变量次数,节点数,和字母数 代码: #include"cstdlib" #include"cstdio" #include"cstring" #include"cmath" #include"

POJ 3691 DNA repair 基于AC自动机的DP

dp[i][j] 表示长度为 i 的前缀到达第 j 个节点的最小更改数目. 很显然有dp[0][0] = 0; dp[ i ][ j ] = min(dp[ i ][ j ],dp[i-1][k] + (j == k ? 0 : 1)),当且仅当j,k满足下列条件时. j 不为某条模式串的末节点 且 j 到 root 的由失败指针组成的路径上无末节点. j 是k的儿子节点 或者 j 的父节点可由 k 沿着失败指针找到. #include <algorithm> #include <ios

[JSOI2007]文本生成器 [AC自动机,dp]

时刻要记住正难则反,可以知道总数是 \(26^m\),我们可以减掉不合法的. AC自动机上面dp,不合法的显然就是没有出现任意的一个串,根据rainy的教导 单词 \(b,bce,abcd\) 的 ACAM 然后 \(dp\) 就好了,由于点数不超过 \(n*m \leq 6000\),然后你每一位枚举复杂度是 \(m^2n\) 的 可以通过本题 // powered by c++11 // by Isaunoya #include <bits/stdc++.h> #define rep(i,

[BZOJ 3530] [Sdoi2014] 数数 【AC自动机+DP】

题目链接:BZOJ - 3530 题目分析 明显是 AC自动机+DP,外加数位统计. WZY 神犇出的良心省选题,然而去年我太弱..比现在还要弱得多.. 其实现在做这道题,我自己也没想出完整解法.. 就想出了个 O(l^3) 的做法: 完全按照数位统计的思想来,先统计长度不足 len 的数字的合法种类数,这个枚举开头,然后 AC 自动机 DP 一下,用 f[i][j] 表示到了第 i 位,在第 j 个节点上的合法数字个数.这样是 O(L^2). 然后长度等于 n 的部分,就按照数位统计,一位位向

【Dream Counting, 2006 Dec-数数的梦】数位dp

题意:给定两个数,问区间[A,B]中0~9分别出现了多少次.A,B<=10^18 题解:应该是最裸的数位dp吧..一开始没有记忆化tle了TAT 我们可以求出区间[0,B]的,再减去区间[0,A]的. 用dfs实现,记录flag(填了的位是否和边界重合),zero(当前是否还在前缀0中) 1 #include<cstdio> 2 #include<cstdlib> 3 #include<cstring> 4 #include<iostream> 5 #

POJ 1625 Censored! (AC自动机 + 高精度 + DP)

题目链接:Censored! 解析:AC自动机 + 高精度 + 简单DP. 字符有可能会超过128,用map映射一下即可. 中间的数太大,得上高精度. 用矩阵快速幂会超时,简单的DP就能解决时间的问题. AC代码: #include <iostream> #include <string.h> #include <algorithm> #include <stdio.h> #include <queue> #include <map>