1. 程式人生 > 其它 >AcWing 1305. GT考試

AcWing 1305. GT考試

題目傳送門

https://www.acwing.com/solution/content/46059/

一、dp(kmp + 狀態機) + 矩陣乘法

這一題是AcWing 1052. 設計密碼的一道擴充套件題目,分析方式仍然是動態規劃。擴充套件方式是資料量,AcWing 1052. 設計密碼中的\(n\)值最大為\(50\),這裡的\(n\)最大可以取到\(10^9\)。這是一種擴充套件方式,還有另外一種擴充套件方式,不擴充套件\(n\),而是讓不能包含多個字串,對應題目是:AcWing 1053. 修復DNA,可以使用\(AC\)自動機解決。

二、解題思路

通過上面的分析,我們根據狀態計算可以得到第i層和第i+1

層之間的關係,即

\[\large f[i+1][0]=f[i][0] * a[0][0] + f[i][1] * a[1][0]+...+f[i][m-1] * a[m-1][0] \\ f[i+1][1]=f[i][0] * a[0][1] + f[i][1] * a[1][1]+...+f[i][m-1] * a[m-1][1] \\ ... \\ f[i+1][m-1]=f[i][0] * a[0][m-1] + f[i][1] * a[1][m-1]+...+f[i][m-1] * a[m-1][m-1] \]

令: $$\large F[i+1]=(f[i+1][0],f[i+1][1],...,f[i+1][m-1])$$

\[\large A= \begin{bmatrix} 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} \end{bmatrix} \]

則有:

\[\large F(i+1)=F(i)*A \]

展開為:

\[\large \begin{bmatrix} f(i+1,0),f(i+1,1),...,f(i+1,m-1) \end{bmatrix} = \\ \begin{bmatrix} f(i,0),f(i,1),...,f(i,m-1) \end{bmatrix} \times \begin{bmatrix} 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} \end{bmatrix}\]

根據上面的分析可知,矩陣\(A\)

只與不合法串\(S\)有關,因此\(A\)矩陣是不變的。根據上面遞推式可知:

\[\large F(n)=F(0)×A^n \ F(0)=[1,0,0,…] \]

如何求解陣列\(A\)呢?如果從\(f(i, j)\)可以轉移到\(f(i+1, k)\),則讓\(a[j, k]++\)。即讓\(f(i+1, k) += f(i, j)\)

求出向量\(F(n)\)後,最後的答案就是向量\(F(n)\)中所有的元素之和。

這是一類問題,凡是動態規劃中兩層之間的轉移形式是乘以一個固定矩陣的,都可以使用快速冪優化。

三、實現程式碼

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 25;

int n, m, mod;
char str[N];
int ne[N];
int a[N][N];

// 本題的正解:kmp+狀態機+dp

//矩陣乘法
void mul(int a[][N], int b[][N], int c[][N]) {
    int t[N][N] = {0};
    for (int i = 0; i < N; ++i) {
        for (int j = 0; j < N; ++j)
            for (int k = 0; k < N; ++k)
                t[i][j] = (t[i][j] + (LL)(a[i][k] * b[k][j]) % mod) % mod;
    }
    memcpy(c, t, sizeof t);
}

int main() {
    cin >> n >> m >> mod;
    cin >> str + 1;

    // kmp
    for (int i = 2, j = 0; i <= m; i++) {
        while (j && str[j + 1] != str[i]) j = ne[j];
        if (str[j + 1] == str[i]) j++;
        ne[i] = j;
    }

    // 初始化A[i][j]
    for (int j = 0; j < m; j++)
        for (int c = '0'; c <= '9'; c++) {
            int k = j;
            while (k && str[k + 1] != c) k = ne[k];
            if (str[k + 1] == c) k++;
            if (k < m) a[j][k]++;
        }

    // f[0][0]=1 base case
    int f[N][N] = {1};
    //矩陣快速冪
    for (int i = n; i; i >>= 1) {
        if (i & 1) mul(f, a, f); // f:基底 a:需要計算快速冪的常數矩陣 f:結果儲存
        mul(a, a, a);            //倍增a
    }

    int res = 0;
    for (int i = 0; i < m; i++) res = (res + f[0][i]) % mod;
    cout << res << endl;

    return 0;
}