luogu P3311 [SDOI2014]数数

P3311 [SDOI2014]数数

题目大意

题目中已经说得比较清楚了吧
就是求小于等于n的数字中,不包含给出一些字串的数的个数
m o d      1 0 9 + 7 mod \ \ \ \ 10^9+7 mod    109+7
就这样

题解

也是先按照套路建AC自动机,然后数位DP
要开4维
分别表示
第几位 节点 limit 是否匹配到
因为有前导0,所以要多开一维
所以是
第几位 节点 limit 是否匹配到 zero

然后就可以愉快地开始DP了

// luogu-judger-enable-o2
#include<bits/stdc++.h>
#define mod 1000000007
#define N 1505
#define int long long
#define C 10
using namespace std;
int ch[N][C], nxt[N * C], vis[N], a[N], tot, L, R, K, n;
unsigned int dp[1203][N][2][2][2];
string st;
void insert(){
	int len = st.length(), p = 0;
	for(int i = 0; i < len; i ++){
		if(!ch[p][st[i] - '0']) ch[p][st[i] - '0'] = ++ tot;
		p = ch[p][st[i] - '0'];
	}
	vis[p] = 1;
}
queue<int> q;
void build(){
	int p = 0;
	for(int i = 0; i < 10; i ++) if(ch[p][i]) q.push(ch[p][i]);
	while(q.size()){
		int u = q.front(); q.pop();
		for(int i = 0; i < 10; i ++){
			if(ch[u][i]){
				nxt[ch[u][i]] = ch[nxt[u]][i];
				vis[ch[u][i]] |= vis[ch[nxt[u]][i]];
				q.push(ch[u][i]);
			} else ch[u][i] = ch[nxt[u]][i];
		}
	}
}
void solve(){
	memset(dp, 0, sizeof dp);
	cin >> st;
	int ws = st.length();
	for(int i = 1; i <= ws; i ++) a[i] = st[i - 1] - '0';
	cin >> n;
	for(int i = 1; i <= n; i ++){
		cin >> st;
		insert();
	}
	build();
	dp[0][0][1][0][1] = 1; //	第几位   节点   limit  是否匹配到   zero
	for(int i = 0; i < ws; i ++){//第几位
		for(int j = 0; j <= tot; j ++){//AC自动机上的节点
			for(int limit = 0; limit <= 1; limit ++){//是否到上界
				for(int zero = 0; zero <= 1; zero ++){//是否有前导0
					for(int pp = 0; pp <= 1; pp ++){//是否匹配到
						if(!dp[i][j][limit][pp][zero]) continue;//蜜汁剪枝
						for(int k = 0; k < 10; k ++){//枚举i+1位是什么数字来转移
							int v = ch[j][k];
							if(zero) v = ch[0][k];
							if(limit && k == a[i + 1]){//看有没有到上界
								dp[i + 1][v][1][vis[v] | pp][zero && (!k)] += dp[i][j][limit][pp][zero], dp[i + 1][v][1][vis[v] | pp][zero && (!k)] %= mod;//转移
								break;//到上界就break
							}else dp[i + 1][v][0][vis[v] | pp][zero && (!k)] += dp[i][j][limit][pp][zero], dp[i + 1][v][0][vis[v] | pp][zero && (!k)] %= mod;//正常的转移
						}
					}
				}
			}
		}
	}
	long long ret = 0;
	for(int i = 0; i <= tot; i ++) ret += dp[ws][i][0][0][1] + dp[ws][i][1][0][1] + dp[ws][i][0][0][0] + dp[ws][i][1][0][0], ret %= mod;//累加答案
	cout << (ret - 1 + mod) % mod;//记得减一,不算0
}
signed main(){
	solve();
	return 0;
}

坑点

前导零……

posted @ 2019-08-09 14:22  lahlah  阅读(28)  评论(0编辑  收藏  举报