[Luogu P3311] [BZOJ 3530] [SDOI2014]数数

36 篇文章 1 订阅
10 篇文章 0 订阅
洛谷传送门
BZOJ传送门

题目描述

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

输入输出格式

输入格式:

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

输出格式:

输出一行一个整数,表示答案模 1 0 9 + 7 10^9+7 109+7的值。

输入输出样例

输入样例#1:
20
3
2
3
14
输出样例#1:
14

说明

下表中 l l l表示 N N N的长度, L L L表示 S S S中所有串长度之和。

1 ≤ l ≤ 1200 , 1 ≤ M ≤ 100 , 1 ≤ L ≤ 1500 1 \le l \le 1200 , 1 \le M \le 100 ,1 \le L \le 1500 1l1200,1M100,1L1500

解题分析

一眼 A C AC AC自动机上 d p dp dp。 再仔细一看, 似乎有点数位 d p dp dp的味道?那就模仿数位 d p dp dp再记一个是否达到上界就可以了。

特别注意不能出现的串可能有前缀 0 0 0, 然后形如 000058 000058 000058实际上是没有前缀 0 0 0的, 所以可以在 0 0 0号节点特判而不特殊考虑前缀 0 0 0

代码如下:

#include <cstdio>
#include <cctype>
#include <cmath>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <queue>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define MX 1505
#define MOD 1000000007
IN void add(R int ad, int &tar) {tar += ad; if (tar > MOD) tar -= MOD;}
int n, cnt, root;
char buf[MX], mod[MX];
int son[MX][10], fail[MX], dp[MX][MX][2];
bool tag[MX];
std::queue <int> q;
void insert(char *str)
{
    R int len = std::strlen(str), now = root, id;
    for (R int i = 0; i < len; ++i)
    {
        id = str[i] - '0';
        if (!son[now][id]) son[now][id] = ++cnt;
        now = son[now][id];
    }
    tag[now] = true;
}
void build()
{
    R int now, tar;
    for (R int i = 0; i < 10; ++i) if (son[0][i]) q.push(son[0][i]);
    W (!q.empty())
    {
        now = q.front(); q.pop();
        for (R int i = 0; i < 10; ++i)
        if (son[now][i])
        {
            q.push(son[now][i]); tar = fail[now];
            fail[son[now][i]] = son[tar][i];
            tag[son[now][i]] |= tag[son[tar][i]];
        }
        else son[now][i] = son[fail[now]][i];
    }
}
void DP()
{
    R int nex, len = std::strlen(mod + 1), lim;
    R int i, j, k;
    for (i = 0; i < len; ++i)
    {
        nex = i + 1, lim = mod[nex] - '0';
        for (j = 0; j <= cnt; ++j)
        {
            if (dp[i][j][1])//limited
            {
                for (k = 0; k < lim; ++k) if (!tag[son[j][k]])
                add(dp[i][j][1], dp[nex][son[j][k]][0]);
                if (!tag[son[j][lim]])
                add(dp[i][j][1], dp[nex][son[j][lim]][1]);
            }
            if (dp[i][j][0])//unlimited
            {
                for (k = 0; k < 10; ++k) if (!tag[son[j][k]])
                add(dp[i][j][0], dp[nex][son[j][k]][0]);
            }
            if (!j)//root
            {
                if (!i)
                {
                    for (k = 1; k < lim; ++k) if (!tag[son[j][k]]) add(1, dp[nex][son[j][k]][0]);
                    if (!tag[son[j][lim]]) add(1, dp[nex][son[j][lim]][1]);
                }
                else
                {
                    for (k = 1; k < 10; ++k) if (!tag[son[j][k]])
                    add(1, dp[nex][son[j][k]][0]);
                }
            }
        }
    }
    int ans = 0;
    for (R int i = 0; i <= cnt; ++i) add(dp[len][i][0], ans), add(dp[len][i][1], ans);
    printf("%d", ans);
}
int main(void)
{
    scanf("%s%d", mod + 1, &n);
    W (n--) scanf("%s", buf), insert(buf);
    build(), DP();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值