BZOJ3530 [SDOI2014]数数

58 篇文章 0 订阅

Time Limit: 10 Sec Memory Limit: 512 MB

Description

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

Input

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

Output

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

Sample Input

20
3
2
3
14

Sample Output

14

HINT

下表中l表示N的长度,L表示S中所有串长度之和。
1 < =l < =1200 , 1 < =M < =100 ,1 < =L < =1500

【原题地址】

BZOJ3530 洛谷P3311

【分析】AC自动机 + DP

先用 AC 自动机对 S 集合的字符串建立Trie树,记数组 nxt bia表示 AC 自动机的失败指针,数组 lst[x] 在点 x 表示沿着失败指针能否走到一个S集合中的串末尾,如有则记为这个字符串的编号。
然后我们记 f[i][j][k] 表示当前已经确定到第 i 位数,走到AC自动机上的节点 j ,且状态为k的总方案数(因为要处理前导0的问题,这里的第 i 位从高位向低位计算,当取最高位,即i=1时,应不包含取0的方案数)。
对于状态 k ,我们有如下定义:
[1]. k=0 已经确定的 i 位数小于N的前 i 位;
[2]. k=1 已经确定的 i 位数等于N的前 i 位;
[3]. k=2 已经确定的 i 位数大于N的前 i 位。
接下来我们考虑如何转移:枚举第i+1位选取的数字 num ,可以利用我们刚才的 AC 自动机判断:这样确定的 i+1 位数字是否存在某个 S 集合中的串是它的后缀。如不存在我们就可以确定AC自动机上的节点 x ,则f[i+1][x][k]就可以由 f[i][j][k] 转移过来,最终的转移如下(可结合此时已经确定的 i 位数与N的前 i 位的大小关系来理解):
[1]. 若num小于 N 的第i+1位:
f[i+1][x][0]+=f[i][j][0]+f[i][j][1]
f[i+1][x][2]+=f[i][j][2]
[2]. 若 num 等于 N 的第i+1位:
f[i+1][x][0]+=f[i][j][0]
f[i+1][x][1]+=f[i][j][1]
f[i+1][x][2]+=f[i][j][2]
[3]. 若 num 大于 N 的第i+1位:
f[i+1][x][0]+=f[i][j][0]
f[i+1][x][2]+=f[i][j][2]+f[i][j][1]
每确定一位数都要将 (f[i+1][x][0]+f[i+1][x][1]+f[i+1][x][2]) 累加进答案 Ans ,同时因为幸运数不能大于 N ,所以最终的答案还要减去f[n][x][2] n 为数N的位数)。

【代码】

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;
typedef long long ll;
const int Maxn = 0x3f3f3f3f;
const int N = 1505, M = 1205;
const ll Mod = 1e9 + 7;
ll f[M][N][3], Ans; 
bool pos[N]; char s[M], a[N]; 
int n, m, T, G[N][10], Q[N], nxt[N], lst[N];

inline int get()
{
    char ch; bool f = false; int res = 0;
    while (((ch = getchar()) < '0' || ch > '9') && ch != '-');
    if (ch == '-') f = true;
     else res = ch - '0';
    while ((ch = getchar()) >='0' && ch <= '9')
        res = (res << 3) + (res << 1) + ch - '0';
    return f? ~res + 1 : res;
}

inline void put(int x)
{
    if (x < 0)
      x = ~x + 1, putchar('-');
    if (x > 9) put(x / 10);
    putchar(x % 10 + 48);
}

inline void Insert()
{
    scanf("%s", a + 1); 
    int x = 0, l = strlen(a + 1);
    for (int i = 1; i <= l; ++i)
    {
        int y = a[i] - '0';
        if (!G[x][y]) G[x][y] = ++T;
        x = G[x][y]; 
    }
    pos[x] = true;
}

inline void Bfs()
{
    int t, w, x, y; t = w = 0; 
    for (int i = 0; i < 10; ++i)
     if (G[0][i]) Q[++w] = G[0][i];
    while (t < w)
    {
        x = Q[++t];
        for (int i = 0; i < 10; ++i)
         if (!G[x][i]) G[x][i] = G[nxt[x]][i];
         else 
         {
            Q[++w] = y = G[x][i]; int v = nxt[x];
            while (!G[v][i] && v) v = nxt[v];
            nxt[y] = G[v][i];
            lst[y] = pos[nxt[y]] ? nxt[y] : lst[nxt[y]]; 
         }
    }
}

inline int cmp(const int &x, const int &y)
{
    if (x > y) return 2;
    if (x < y) return 0;
    return 1; 
}

int main()
{
    scanf("%s", s + 1); 
    n = strlen(s + 1); m = get();
    while (m--) Insert(); Bfs();
    for (int i = 1; i < 10; ++i) //不含前导0 
    {
        int x = G[0][i];
        if (!pos[x] && !lst[x]) f[1][x][cmp(i, s[1] - '0')]++;
    }
    for (int i = 0; i <= T; ++i)
     (Ans += f[1][i][0] + f[1][i][1] + f[1][i][2]) %= Mod;
    for (int i = 1; i < n; ++i)
    {
        for (int j = 0; j <= T; ++j)
        if (f[i][j][0] || f[i][j][1] || f[i][j][2])
        for (int k = 0; k < 10; ++k)
        {
            int x = j;
            while (!G[x][k] && x) x = nxt[x];
            x = G[x][k]; 
            if (pos[x] || lst[x]) continue;
            int v = cmp(k, s[i + 1] - '0');
            switch (v)
            {
                case 0:
                  (f[i + 1][x][0] += f[i][j][0] + f[i][j][1]) %= Mod;
                  (f[i + 1][x][2] += f[i][j][2]) %= Mod; break;
                case 1:
                  (f[i + 1][x][0] += f[i][j][0]) %= Mod;
                  (f[i + 1][x][1] += f[i][j][1]) %= Mod;
                  (f[i + 1][x][2] += f[i][j][2]) %= Mod; break;
                case 2: 
                  (f[i + 1][x][2] += f[i][j][2] + f[i][j][1]) %= Mod;
                  (f[i + 1][x][0] += f[i][j][0]) %= Mod; break;
            }
        }
        for (int j = 0; j <= T; ++j)
        {
            (Ans += f[i + 1][j][0] + f[i + 1][j][1]) %= Mod;
            if (i + 1 < n) (Ans += f[i + 1][j][2]) %= Mod;
        }
    }
    return put(Ans), 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值