题目-GT考试

在这里插入图片描述

问题分析

要求生成字符串并且不包含 s s s, 求方案数量
数据范围 N ≤ 50 N \le 50 N50的时候, 参考基础动态规划问题(1)设计密码问题, 状态转移方程是 f ( i + 1 , k ) = f ( i + 1 , k ) + f ( i , j ) f(i + 1, k) = f(i + 1, k) + f(i, j) f(i+1,k)=f(i+1,k)+f(i,j)

但是发现本题的数据范围 1 0 9 10 ^ 9 109, 上述做法的时间复杂度 O ( n m ) O(nm) O(nm), 一定会超时的, 需要进行优化

  • 考虑状态表示 f ( i , j ) f(i, j) f(i,j)表示, 生成了长度为 i i i的字符串, 并且不包含串 s s s, 并且与 s s s匹配的最长公共前后缀长度是 j j j的一类字符串, 属性是 c n t cnt cnt

  • 假设当前状态是 f ( i , j ) f(i, j) f(i,j), 那么在当前字符串结尾加入一个字符 c c c, 看KMP算法的 j j j指针( j = n e [ i − 1 ] j = ne[i - 1] j=ne[i1])能指向什么位置, 假设是 k k k, 也就是s[k] == c, 那么

f ( i + 1 , k ) = f ( i + 1 , k ) + f ( i , j ) f(i + 1, k) = f(i + 1, k) + f(i, j) f(i+1,k)=f(i+1,k)+f(i,j)

m m m s s s的长度, 将所有式子展开( j j j指针指向 m m m的时候匹配上, 因此最远只能指向 m − 1 m - 1 m1位置)

f ( i + 1 , 0 ) = a 0 , 0 f ( i , 0 ) + a 0 , 1 f ( i , 1 ) + . . . + a 0 , m − 1 f ( i , m − 1 ) f(i + 1, 0) = a_{0, 0}f(i, 0) +a_{0, 1}f(i, 1) + ... + a_{0, m - 1}f(i, m - 1) f(i+1,0)=a0,0f(i,0)+a0,1f(i,1)+...+a0,m1f(i,m1)
f ( i + 1 , 1 ) = a 1 , 0 f ( i , 0 ) + a 1 , 1 f ( i , 1 ) + . . . + a 1 , m − 1 f ( i , m − 1 ) f(i + 1, 1) = a_{1, 0}f(i, 0) +a_{1, 1}f(i, 1) + ... + a_{1, m - 1}f(i, m - 1) f(i+1,1)=a1,0f(i,0)+a1,1f(i,1)+...+a1,m1f(i,m1)
f ( i + 1 , 2 ) = a 2 , 0 f ( i , 0 ) + a 2 , 1 f ( i , 1 ) + . . . + a 2 , m − 1 f ( i , m − 1 ) ⋮ f(i + 1, 2) = a_{2, 0}f(i, 0) +a_{2, 1}f(i, 1) + ... + a_{2, m - 1}f(i, m - 1) \\ \vdots f(i+1,2)=a2,0f(i,0)+a2,1f(i,1)+...+a2,m1f(i,m1)

关键点:
假设 f ( i , j ) f(i, j) f(i,j)的方案数是 x x x, 状态机从 j j j转移到 k k k的方案数是 y y y, 那么 f ( i + 1 , k ) f(i + 1, k) f(i+1,k)需要累加 x × y x \times y x×y的方案数
f ( i + 1 , k ) = ∑ f ( i , j ) × t f(i + 1, k) = \sum {f(i, j) \times t} f(i+1,k)=f(i,j)×t
t t t状态机 j j j转移到 k k k的方案数

F ( i ) = [ f ( i , 0 ) , f ( i , 1 ) , . . . , f ( i , m − 1 ) ] F(i) = [f(i, 0), f(i, 1), ..., f(i, m - 1)] F(i)=[f(i,0),f(i,1),...,f(i,m1)], 那么有 F ( i + 1 ) = F ( i ) × A F(i + 1) = F(i) \times A F(i+1)=F(i)×A
其中
A = [ a 0 , 0 a 0 , 1 … a 0 , m − 1 a 1 , 0 a 1 , 1 … a 1 , m − 1 … … … … a m − 1 , 0 a m − 1 , 1 … a m − 1 , m − 1 ] A = \left[\begin{array}{cccc} a_{0,0} & a_{0,1} & \ldots & a_{0, m-1} \\ a_{1,0} & a_{1,1} & \ldots & a_{1, m-1} \\ \ldots & \ldots & \ldots & \ldots \\ a_{m-1,0} & a_{m-1,1} & \ldots & a_{m-1, m-1} \end{array}\right] A= a0,0a1,0am1,0a0,1a1,1am1,1a0,m1a1,m1am1,m1

可以将动态规划的过程使用矩阵乘法进行优化, 原来递推计算的 F n F_n Fn, 可以在 log ⁡ n \log n logn复杂度内算出

算法步骤

  • 首先预处理KMP算法的 n e ne ne数组
  • 构建自动机, 计算 A ( i , j ) A(i, j) A(i,j), 如果 j j j能转移到 k k k, 那么 A ( j , k ) = A ( j , k ) + 1 A(j, k) = A(j, k) + 1 A(j,k)=A(j,k)+1
  • 初始状态 F 0 = [ 1 , 0 , 0 , . . . , 0 ] F_0 = [1, 0, 0, ..., 0] F0=[1,0,0,...,0], 快速幂计算 F n F_n Fn
  • 实现矩阵快速幂算法

算法时间复杂度 O ( m log ⁡ n ) O(m\log n) O(mlogn)

代码实现

#include <bits/stdc++.h>

using namespace std;

const int N = 25;

int n, m, mod;
string s;
int ne[N], A[N][N];

void init() {
   
    for (int i = 1; i < m; ++i) {
   
        int j = ne[i - 1];
        while (j && s[j] != s[i]) j = ne[j - 1];
        if (s[j] == s[i]) ne[i] = j + 1;
    }

    for (int j = 0; j < m; ++j) {
   
        for (char c = '0'; c <= '9'; ++c) {
   
            int k = j;
            while (k && s[k] != c) k = ne[k - 1];
            if (s[k] == c) k++;
            if (k < m) A[j][k] = (A[j][k] + 1) % mod;
        }
    }
}

void mul(int c[], int a[], int b[][N]) {
   
    static int tmp[N];
    memset(tmp, 0, sizeof tmp);

    for (int i = 0; i < m; ++i) {
   
        for (int j = 0; j < m; ++j) {
   
            tmp[i] = (tmp[i] + a[j] % mod * b[j][i] % mod) % mod;
        }
    }

    memcpy(c, tmp, sizeof tmp);
}

void mul(int c[][N], int a[][N], int b[][N]) {
   
    static int tmp[N][N];
    memset(tmp, 0, sizeof tmp);

    for (int i = 0; i < m; ++i) {
   
        for (int j = 0; j < m; ++j) {
   
            for (int k = 0; k < m; ++k) {
   
                tmp[i][j] = (tmp[i][j] + a[i][k] % mod * b[k][j] % mod) % mod;
            }
        }
    }

    memcpy(c, tmp, sizeof tmp);
}

// F[n] = F[0] * A ^ n
void solve() {
   
    int f0[N] = {
   0};
    f0[0] = 1;

    int k = n;
    while (k) {
   
        if (k & 1) mul(f0, f0, A);
        mul(A, A, A);
        k >>= 1;
    }

    int ans = 0;
    for (int i = 0; i < m; ++i) ans = (ans + f0[i]) % mod;

    cout << ans << '\n';
}

int main() {
   
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n >> m >> mod;
    for (int i = 0; i < m; ++i) {
   
        char c;
        cin >> c;
        s += c;
    }

    init();
    solve();

    return 0;
}