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

本文介绍如何利用AC自动机构建状态并结合数位DP解决问题。定义状态f[i][u]表示到了第i位,走到AC自动机上的节点u。通过定义orz[u]判断状态合法性,最终实现对所有模式串的匹配。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值