[HNOI2008] GT考试

思路

状态转移矩阵T[i][j]其实就是添加一个字符之后从状态i转移到状态j的方案数量,也就是先用kmp得到对应的T然后对于长度为N的串只需要让这个转移矩阵N次方就行了,然后Tn[0][j]就是子串最大匹配到j位的方案数,所求结果就是Tn[0][0]一直加到Tn[0][M-1](M是子串的长度)

算法

KMP 自动机建模 + 矩阵快速幂

主要函数以及主要流程

  • 矩阵相乘函数
  • 矩阵快速幂函数(利用到矩阵相乘函数快速幂算法)
  • kmp算法(过于基础就不介绍了)
  • 利用next函数得到基础转移矩阵T[m][n]
  • 对T运用幂n函数得到Tn
  • Tn按照思路直接操作就行了

题解(借鉴了AI,自己敲的,我太强了)

#include<bits/stdc++.h>

using namespace std;

typedef long long ll;

// 得到next函数
vector<int> getNext(string pattern)
{
    int m = pattern.size();
    vector<int> next(m,0);
    for(int i=1,j=0;i<m;i++)
    {
        while(j!=0 && pattern[i] != pattern[j]) j = next[j-1];
        if(pattern[j] == pattern[i]) j++;
        next[i] = j;
    }
    return next;
}

// 矩阵相乘函数
vector<vector<int>> matMul(vector<vector<int>> a,vector<vector<int>> b,int K)
{
    int m = a.size();
    vector<vector<int>> res(m,vector<int>(m,0)); //结果
    for(int i=0;i<m;i++) // i-k,k-j
    {
        for(int k=0;k<m;k++)
        {
            if(a[i][k]!=0) //剪枝减小工作量
            {
                for(int j=0;j<m;j++)
                {
                    res[i][j] = (res[i][j] + (ll)a[i][k] * b[k][j]) % K;
                }
            }
        }
    }
    return res;
}

// 矩阵幂函数(利用快速幂算法)
vector<vector<int>> matPow(vector<vector<int>> a,int p,int K)
{
    int m = a.size();
    vector<vector<int>> res(m,vector<int>(m,0)); //结果
    for(int i=0;i<m;i++) res[i][i] = 1; //单位矩阵用于起步
    while(p!=0)
    {
        if(p & 1) res = matMul(res,a,K);
        a = matMul(a,a,K);
        p >>= 1; // p /= 2
    }
    return res;
}

int main()
{
    int N,M,K;
    // M<=20 N<=1e9 K<=10000,所以不能暴力解
    cin >> N >> M >> K;
    string pattern; // 子串(此题中指的是不该出现的子串)
    cin >> pattern;

    // 求 pattern 的状态转移方程T[i][j]
    // 因为要用到pattern的next数组,所以先求next
    vector<int> next = getNext(pattern);
    vector<vector<int>> T(M,vector<int>(M,0)); // T[M][M],表示基础转移矩阵
    for(int m=0;m<M;m++) // m为当前匹配数量,由于不准出现匹配到完整M的,所以最大长度为M-1
    { 
        for(int digit=0;digit<=9;digit++) // 尝试每一个数字,从而进行状态转移
        {
            int cur = m; // 从当前位置开始回退
            while(cur!=0 && (pattern[cur]-'0') != digit) cur = next[cur-1];
            if((pattern[cur]-'0') == digit) cur++;
            if(cur < M) T[m][cur] = (T[m][cur] + 1) % K;
        }
    }

    // 得到完整的状态转移矩阵
    vector<vector<int>> Tn = matPow(T,N,K);

    ll result = 0;
    for(int i=0;i<M;i++)
    {
        result = (result + Tn[0][i]) % K;
    }

    cout << result << endl; 
    return 0;
}

遇到问题

  • p>>1不会改变p的值应该是p>>=1
  • int类型乘法要注意防止溢出,一般用ll