[BZOJ3530][Sdoi2014]数数(AC自动机上数位DP)

把所有的模式串构建成一个AC自动机。
定义状态: f[i][u] f [ i ] [ u ] 表示到了第 i i 位,走到AC自动机上的节点u
「走到AC自动机上的节点 u u 」的具体含义:节点u代表的串是数字串的一个后缀,并且是数字串的所有后缀中能被AC自动机识别(一个串 S S 能被AC自动机识别意为AC自动机上存在一个节点代表串S)的最长后缀。
怎样判断一个状态是否合法(不包含所有的模式串作为子串)呢?可以发现,对于一个状态 f[i][u] f [ i ] [ u ] ,只需要保证节点 u u 代表的字符串的所有后缀都不等于任何一个模式串。所以定义orz[u],表示节点 u u 不断地往fail指针走,能否走到一个模式串的末尾。这时候只要满足orz[u]=false,那么 f[i][u] f [ i ] [ u ] 这个状态就是合法的。
然而由于要求不大于 N N ,所以加上一维,进行数位DP:
f[i][u][0]:前 i i 位小于N的前 i i 位。
f[i][u][1]:前 i i 位等于N的前 i i 位。
f[i][u][2]:前 i i 位大于N的前 i i 位。
转移即枚举第i+1位的取值 x x ,下面定N的第 i+1 i + 1 位的值为 y y v为在AC自动机上节点 u u 通过为x的边转移到的节点,分类讨论:
1、 x<y x < y
f[i+1][v][0]+=f[i][u][0]+f[i][u][1] f [ i + 1 ] [ v ] [ 0 ] + = f [ i ] [ u ] [ 0 ] + f [ i ] [ u ] [ 1 ]
f[i+1][v][2]+=f[i][u][2] f [ i + 1 ] [ v ] [ 2 ] + = f [ i ] [ u ] [ 2 ]
2、 x>y x > y
f[i+1][v][2]+=f[i][u][2]+f[i][u][1] f [ i + 1 ] [ v ] [ 2 ] + = f [ i ] [ u ] [ 2 ] + f [ i ] [ u ] [ 1 ]
f[i+1][v][0]+=f[i][u][0] f [ i + 1 ] [ v ] [ 0 ] + = f [ i ] [ u ] [ 0 ]
3、 x=y x = y
f[i+1][v][0]+=f[i][u][0] f [ i + 1 ] [ v ] [ 0 ] + = f [ i ] [ u ] [ 0 ]
f[i+1][v][1]+=f[i][u][1] f [ i + 1 ] [ v ] [ 1 ] + = f [ i ] [ u ] [ 1 ]
f[i+1][v][2]+=f[i][u][2] f [ i + 1 ] [ v ] [ 2 ] + = f [ i ] [ u ] [ 2 ]
(注意第 1 1 位不能为0
最后结果:
l1i=1u2k=0f[i][u][k]+u(f[l][u][0]+f[l][u][1]) ∑ i = 1 l − 1 ∑ u ∑ k = 0 2 f [ i ] [ u ] [ k ] + ∑ u ( f [ l ] [ u ] [ 0 ] + f [ l ] [ u ] [ 1 ] )
代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1550, MX = 1e9 + 7;
char s[N], ed[N];
int n, m, l, QAQ, len, que[N], f[N][N][3];
bool orz[N];
struct cyx {
    int cnt, nxt, go[10];
    void init() {
        cnt = nxt = 0;
        memset(go, 0, sizeof(go));
    }
} T[N];
void ins() {
    int i, u = 1; for (i = 1; i <= l; i++) {
        int c = ed[i] - '0';
        if (!T[u].go[c]) T[T[u].go[c] = ++QAQ].init();
        u = T[u].go[c];
    }
    T[u].cnt++;
}
void buildFail() {
    int i, c; for (i = que[len = 1] = 1; i <= len; i++) {
        int u = que[i];
        for (c = 0; c < 10; c++) {
            int v = T[u].go[c]; if (!v) {
                T[u].go[c] = T[T[u].nxt].go[c];
                continue;
            }
            int w = T[u].nxt; while (!T[w].go[c]) w = T[w].nxt;
            T[v].nxt = T[w].go[c]; que[++len] = v;
            orz[v] = (T[v].cnt > 0) || orz[T[v].nxt];
        }
    }
}
int cx(int i, int j) {
    if (i < j) return 0; if (i > j) return 2; return 1;
}
void DP() {
    int i, u, c; for (i = 1; i < 10; i++) {
        u = T[1].go[i]; if (!orz[u])
            f[1][u][cx(i, s[1] - '0')]++;
    }
    for (i = 1; i < n; i++) for (u = 1; u <= QAQ; u++) if (!orz[u])
    for (c = 0; c < 10; c++) {
        int v = T[u].go[c], op = cx(c, s[i + 1] - '0');
        if (!orz[v]) {
            if (op == 0) {
                f[i + 1][v][0] = (f[i + 1][v][0] + f[i][u][0]) % MX;
                f[i + 1][v][0] = (f[i + 1][v][0] + f[i][u][1]) % MX;
                f[i + 1][v][2] = (f[i + 1][v][2] + f[i][u][2]) % MX;
            }
            else if (op == 2) {
                f[i + 1][v][2] = (f[i + 1][v][2] + f[i][u][2]) % MX;
                f[i + 1][v][2] = (f[i + 1][v][2] + f[i][u][1]) % MX;
                f[i + 1][v][0] = (f[i + 1][v][0] + f[i][u][0]) % MX;
            }
            else {
                f[i + 1][v][1] = (f[i + 1][v][1] + f[i][u][1]) % MX;
                f[i + 1][v][0] = (f[i + 1][v][0] + f[i][u][0]) % MX;
                f[i + 1][v][2] = (f[i + 1][v][2] + f[i][u][2]) % MX;
            }
        }
    }
}
int main() {
    int i, j, ans = 0; scanf("%s", s + 1); n = strlen(s + 1); cin >> m;
    T[QAQ = 1].init(); T[0].init(); for (i = 0; i < 10; i++) T[0].go[i] = 1;
    for (i = 1; i <= m; i++)
        scanf("%s", ed + 1), l = strlen(ed + 1), ins();
    buildFail(); DP(); for (i = 1; i < n; i++) for (j = 1; j <= QAQ; j++) {
        ans = (ans + f[i][j][0]) % MX; ans = (ans + f[i][j][1]) % MX;
        ans = (ans + f[i][j][2]) % MX;
    }
    for (i = 1; i <= QAQ; i++)
        ans = (ans + f[n][i][0]) % MX, ans = (ans + f[n][i][1]) % MX;
    cout << ans << endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值