1. 程式人生 > >[HNOI2008] GT考試(DP+矩陣快速冪+KMP)

[HNOI2008] GT考試(DP+矩陣快速冪+KMP)

ref char sin span 題目 -o 原本 沒有 urn

題目鏈接:https://www.luogu.org/problemnew/show/P3193#sub

題目描述

阿申準備報名參加 GT 考試,準考證號為 N 位數 X1,X2…Xn(0 <= Xi <= 9) ,他不希望準考證號上出現不吉利的數字。 他的不吉利數學 A1?,A2?Am?(0Ai?9) 有 M 位,不出現是指 X1?,X2?Xn? 中沒有恰好一段等於 A1?,A2?Am?A1?

輸入輸出格式

輸入格式:

第一行輸入N,M,K.接下來一行輸入M位的數。

輸出格式:

阿申想知道不出現不吉利數字的號碼有多少種,輸出模 K 取余的結果。

輸入輸出樣例

輸入樣例#1:
4 3 100
111
輸出樣例#1:
81

說明

N109,M20,K1000

題目大意,給定長為m的子串,統計長度為n的不包含該子串的串的方案數

考慮DP解決,f[i][j]表示長串匹配到第 i 位,短串最多可以匹配到第 j 位的方案數(即表示長度為i的長串,最後j個可以匹配短串前j位的方案數)

狀態轉移方程如下:

f[i+1][j]=f[i][k]*g[j][k](0<=k<m)

最終答案就是?f[n][i](0<=i<m)(這顯然正確,仔細想想就發現這些i代表的狀態互相獨立,且並集包含了所有的狀態)

g[i][j]表示對於短串,原本匹配了i位,匹配下一位時匹配到第j位的這個下一位的方案數

技術分享圖片

圖一,短串匹配了j位,長串匹配到了i位

技術分享圖片

圖2,長串繼續向下匹配,短串失配

技術分享圖片

圖3,短串轉移到下一個可以匹配的地方

註意由於new是我們任意填的,因此我們只需考慮短串的下一個匹配的位置,即KMP算法中的next數組

上面三幅圖實際上就是匹配的過程,是為了讓讀者更好的理解g數組的含義

下面我們考慮怎麽求g數組。回顧g數組的含義,我們發現實際上只和短串有關(上面說了,new是任意填的)。KMP預處理出g數組,若我原來匹配了i位,枚舉下一個數字,不斷轉移next數組直到匹配成功,最終得到一個可以匹配的位置k,然後我們讓f[i][k]++統計方案數

發現n的取值過大且上述狀態轉移方程可用矩陣快速冪優化。註意每次乘上轉移矩陣得到的矩陣存儲的實際上是狀態,因此其實矩陣的寬都是1來著。

考慮到每次我們轉移的矩陣g是不變的,於是我們可以很快結束這個問題

代碼如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;

const int N=25;
int n,m,mod;
int next[N],a[N];
char s[N];
struct matrix
{
    int r,c,num[N][N];//矩陣的長寬 
    matrix(){memset(num,0,sizeof(num));}
    void init()
    {
        for (int i=0;i<N;i++)
            num[i][i]=1;
    }
}g,A;
matrix mul(matrix a,matrix b)
{
    matrix ans;
    ans.r=a.r;ans.c=b.c;
    for (int i=0;i<=a.r;i++)
        for (int j=0;j<=b.c;j++)
        {
            ans.num[i][j]=0;
            for (int k=0;k<=ans.c;k++)
            ans.num[i][j]=(ans.num[i][j]+a.num[i][k]*b.num[k][j])%mod;
        }
    return ans;
}
matrix qpow(matrix a,int x)
{
    matrix ans;
    ans.init();
    for (;x;x>>=1,a=mul(a,a)) if (x&1) ans=mul(ans,a);
    return ans;
}
int main()
{
    scanf("%d%d%d",&n,&m,&mod);
    scanf("%s",s);
    for (int i=0;i<m;i++) a[i]=s[i]-0;a[m]=0x3f3f3f3f;
    for (int i=1,j=0;i<m;i++)//計算出next數組 
    {
        while (j&&(a[i]!=a[j])) j=next[j];
        j+=(a[i]==a[j]);
        next[i+1]=j;
    }
    for (int i=0;i<m;i++)
        for (int j=0;j<10;j++)//預處理出g數組 
        {
            int k=i;
            while (k&&a[k]!=j) k=next[k];
            k+=(a[k]==j);
            if (k<m) g.num[i][k]++;//i位可以轉移到k位 
        }
    A.num[0][0]=1;A.r=0;g.c=g.r=A.c=m-1;//初始化 
    A=mul(A,qpow(g,n));
    int ans=0;
    for (int i=0;i<m;i++) {ans+=A.num[0][i];ans%=mod;}//統計每個狀態的答案 
    printf("%d",ans);
    return 0;
}

[HNOI2008] GT考試(DP+矩陣快速冪+KMP)