【HNOI2008】【BZOJ1009】T考试

【题目链接】

【前置技能】

  • DP
  • 矩阵乘法
  • AC自动机
  • KMP

【题解】

  • 预处理出每一位后面填 0 0 0~ 9 9 9可以走到哪一位,DP状态: f [ i ] [ j ] f[i][j] f[i][j]表示现在是第 i i i位数字,匹配到第 j j j位。转移比较显然,不多赘述。因为 n n n比较大,所以要用矩阵乘法优化转移。这里觉得AC自动机写起来比较方便,所以代码给出的是AC自动机的写法。
  • 时间复杂度 O ( M 3 l o g N ) O(M^3logN) O(M3logN)

【代码】

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define LL  long long
#define MAXN    22
#define MAXLOG  30
using namespace std;
int n, m, ans, mod;
char s[MAXN];
int mat[MAXLOG + 1][MAXN][MAXN], f[1][MAXN], tmp[1][MAXN];
 
template <typename T> void chkmin(T &x, T y){x = min(x, y);}
template <typename T> void chkmax(T &x, T y){x = max(x, y);}
template <typename T> void read(T &x){
    x = 0; int f = 1; char ch = getchar();
    while (!isdigit(ch)) {if (ch == '-') f = -1; ch = getchar();}
    while (isdigit(ch)) {x = x * 10 + ch - '0'; ch = getchar();}
    x *= f;
}
 
void update(int &x, int y){
    x += y;
    if (x >= mod) x -= mod;
}
 
int mul(int x, int y){
    return 1ll * x * y % mod;
}
 
struct AC_Automaton{
    struct info{int son[10], fail, tag;}a[MAXN];
    int cnt;
    void ins(char *s){
        int len = strlen(s + 1);
        int pos = 0;
        for (int i = 1; i <= len; ++i){
            int ch = s[i] - '0';
            if (!a[pos].son[ch]) a[pos].son[ch] = ++cnt;
            pos = a[pos].son[ch];
        }
        a[pos].tag = 1;
    }
    void build(){
        static int q[MAXN], l = 0, r = -1;
        for (int ch = 0; ch < 10; ++ch)
            if (a[0].son[ch]) q[++r] = a[0].son[ch];
        while (l <= r){
            int pos = q[l++];
            for (int ch = 0; ch < 10; ++ch)
                if (a[pos].son[ch]) {
                    a[a[pos].son[ch]].fail = a[a[pos].fail].son[ch];
                    a[a[pos].son[ch]].tag |= a[a[a[pos].son[ch]].fail].tag;
                    q[++r] = a[pos].son[ch];
                } else a[pos].son[ch] = a[a[pos].fail].son[ch];
        }
    }
    int go(int pos, int ch){
        return a[pos].son[ch];
    }
    void work(){
        for (int pos = 0; pos <= cnt; ++pos){
            if (a[pos].tag) continue;
            for (int ch = 0; ch < 10; ++ch){
                int nxt = go(pos, ch);
                if (a[nxt].tag) continue;
                update(mat[0][pos][nxt], 1);
            }
        }
        for (int p = 1; p <= MAXLOG; ++p)
            for (int i = 0; i <= cnt; ++i)
                for (int j = 0; j <= cnt; ++j)
                    for (int t = 0; t <= cnt; ++t)
                        update(mat[p][i][j], mul(mat[p - 1][i][t], mat[p - 1][t][j]));
        f[0][0] = 1;
        for (int p = 1; p <= MAXLOG; ++p)
        if (n & (1 << p)) {
            memset(tmp, 0, sizeof(tmp));
            for (int i = 0; i <= 0; ++i)
                for (int j = 0; j <= cnt; ++j)
                    for (int t = 0; t <= cnt; ++t)
                        update(tmp[i][j], mul(f[i][t], mat[p][t][j]));
            for (int i = 0; i <= 0; ++i)
                for (int j = 0; j <= cnt; ++j)
                    f[i][j] = tmp[i][j];
        }
        ans = 0;
        for (int pos = 0; pos <= cnt; ++pos)
            if (!a[pos].tag) update(ans, f[0][pos]);
    }
}ACAM;
 
int main(){
    read(n), read(m), read(mod);
    scanf("%s", s + 1);
    ACAM.ins(s);
    ACAM.build();
    ACAM.work();
    printf("%d\n", ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值