矩阵乘法 —— 洛谷 P3193 [HNOI2008]GT考试

矩阵乘法 —— 洛谷 P3193 [HNOI2008]GT考试

该题与\(IndeedTokyo2019\)校招笔试题涉及密码有相同的思路,都是\(DP\)问题。

思路

由于状态的数量众多,所以我们需要使用状态机模型考虑一大类状态的转移。

使用闫氏\(DP\)分析法,从集合角度分析问题:

  • 状态表示:\(f[i, j]\),表示长度为\(i\)且没有不吉利数字,且与不吉利数字匹配的最大前缀长度为\(j\)的所有字符串。所具有的属性为数量。
  • 状态计算:我们从第\(i\)层转移到第\(i+1\)层需要在第\(i\)层后面添加上一个数字,而由于我们只关心与不吉利数字匹配的最大前缀长度和添加的数字,并不关心内部的数字,所以\(f[i+1,k]=a _ {k0}f[i,0]+a _ {k1}f[i,1]+...\)里的所有系数\(a\)都是不变的因此我们可以使用矩阵快速幂进行优化。

当我们计算中间矩阵\(a\)时,矩阵乘法公式\(f[i+1,k]=\sum^{m-1} _ {j=0}f[i,j] * a[j,k]\),我们我们只需要知道\(^{[1]}\)不吉利数字的长度为\(j\)的前缀加上数字\(0-9\)之后能转移到哪个长度\(k\),然后使\(a[j,k]+1\)即可。

\([1]\):可以使用\(KMP\)来辅助优化。
优化思路:\(ne[j]=k\)表示下标为\(j\)的前面\(k\)个字符与前缀的\(k\)个字符相同,即表示当第\(j\)个字符不匹配时指针将要移动到的位置。所以我们要使第\(j\)个字符之后加上一个数字后与某个长度匹配,只需要使\(k=j+1\),表示前\(j\)个字符都已经匹配,当第\(k\)个字符不等于加上的数字时再移动指针,当指针不移动时就表示已经匹配到了一个长度为\(k\)的前缀,使\(a[j,k]+1\)即可。

代码

const int N = 25;

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

void mul(int c[][N], int a[][N], int b[][N]) 
{
    static int temp[N][N];
    memset(temp, 0, sizeof temp);
    
    for (int i = 0; i < m; i ++ ) 
        for (int j = 0; j < m; j ++ ) 
            for (int k = 0; k < m; k ++ ) 
                temp[i][j] = (temp[i][j] + a[i][k] * b[k][j]) % mod;
    memcpy(c, temp, sizeof temp);
}

int qpow() 
{
    int F[N][N] = {1};
    while (n) 
    {
        if (n & 1) mul(F, F, a);
        mul(a, a, a);
        n >>= 1;
    }
    
    int res = 0;
    for (int i = 0; i < m; i ++ ) res = (res + F[0][i]) % mod;
    return res;
}

int main() 
{
    cin >> n >> m >> mod;
    cin >> str + 1;
    
    // kmp匹配ne数组
    int j = 1, k = 0;
    ne[j] = k;
    while (j <= m) 
    {
        if (k == 0 || str[j] == str[k]) j ++ , k ++ , ne[j] = k;
        else k = ne[k];
    }
    
    // kmp初始化a数组
    for (int j = 0; j < m; j ++ ) 
        for (int c = '0'; c <= '9'; c ++ ) 
        {
            int k = j + 1;
            while (k && str[k] != c) k = ne[k];
            if (k < m) a[j][k] ++ ;
        }
        
    cout << qpow() << endl;
    
    return 0;
}
上一篇:2021-11-08FGUI 使用


下一篇:【Spark】【RDD】从本地文件系统创建RDD