P3311 [SDOI2014]数数
题意
我们称一个正整数N\((len(N) <= 1200)\)是幸运数,当且仅当它的十进制表示中不包含数字串集合S中任意一个元素作为其子串。例如当S=(22,333,0233)时,233是幸运数,2333、20233、3223不是幸运数。给定N和S,计算不大于N的幸运数个数。
题解
在\(Trie\)图上跑数位\(dp\),\(dp[i][j]\) 表示走了\(i\)步到了结点\(j\)的方案数,那么转移的时候分两种情况,①是已经沿着边界走了\(i\)步,②不是沿着边界走了\(i\)步。多想想数位\(dp\)的边界处理方式。然后顺着图转移就行了。从高位向低位枚举数位的过程中会产生前导0,这与{0},{00},{000}会产生冲突,所以是前导0并且当前枚举的 i == 0, 就直接回到根节点
代码
const int mod = 1000000007;
struct node {
bool flag;
int fail, vis[10];
node() {
mem(vis, 0);
flag = fail = 0;
}
};
node a[2000];
struct Acmation {
int tot;
stack<int> st;
void Inite() {
tot = 0;
}
void Insert(char *s) {
int n = strlen(s);
int now = 0;
rep(i, 0, n) {
int id = s[i] - '0';
if (!a[now].vis[id]) a[now].vis[id] = ++tot;
now = a[now].vis[id];
}
a[now].flag = 1;
}
void getFail() {
queue<int> q;
rep(i, 0, 10) if (a[0].vis[i]) {
a[a[0].vis[i]].fail = 0;
q.push(a[0].vis[i]);
}
while(!q.empty()) {
int now = q.front();
q.pop();
rep(i, 0, 10) {
int pre = a[a[now].fail].vis[i];
if (a[now].vis[i]) {
a[a[now].vis[i]].fail = pre;
a[a[now].vis[i]].flag |= a[pre].flag;
q.push(a[now].vis[i]);
}
else a[now].vis[i] = pre;
}
}
}
};
Acmation ac;
char s[2000];
int n, m, dp[1300][1600];
int DFS(int pos, int now, bool limit, bool lead) {
if (pos == n) return 1;
if (!limit && !lead && dp[pos][now] != -1) return dp[pos][now];
int up = (limit ? s[pos] - '0' : 9);
int ans = 0;
Rep(i, 0, up) {
if (!i && lead) ans = (ans + DFS(pos + 1, 0, 0, 1)) % mod;
else {
if (a[a[now].vis[i]].flag) continue;
ans = (ans + DFS(pos + 1, a[now].vis[i], limit && (i == up), lead && (i == 0))) % mod;
}
}
if (!limit && !lead) dp[pos][now] = ans;
return ans;
}
int main()
{
ac.Inite();
scanf("%s", s);
n = strlen(s);
char str[2000];
sc(m);
Rep(i, 1, m) {
scanf("%s", str);
ac.Insert(str);
}
ac.getFail();
mem(dp, -1);
pr(DFS(0, 0, 1, 1) - 1);
}