1. 程式人生 > >bzoj1009-HNOI2008 GT考試 字符串dp+矩陣快速冪

bzoj1009-HNOI2008 GT考試 字符串dp+矩陣快速冪

表示 我們 ios inline gt考試 urn zoj 時間 ostream

  • 題面描述

    • 阿申準備報名參加\(GT\)考試,準考證號為\(N\)位數\(x_1,x_2,...,x_n\ (0\leq x_i\leq 9)\),他不希望準考證號上出現不吉利的數字。
      他的不吉利數字\(a_1,a_2,...,a_m\ (0\leq a_i\leq 9)\)\(M\)位,不出現是指\(x_1,x_2,...,x_n\)中沒有恰好一段等於\(a_1,a_2,...,a_m\)\(a_1\)\(x_1\)可以為\(0\)
  • 輸入格式

    • 第一行輸入\(N,M,K\)。接下來一行輸入\(M\)位的數。 \(N\leq 10^9,M\leq 20,K\leq 1000\)
  • 輸出格式

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

    • 首先,看到題意是在一定條件下統計 位數\(\leq N\)的數 的個數,第一反應數位\(dp\)。題目對要統計的數的要求是 這個數不能與模式串(不吉利數字)匹配。我們回憶\(KMP\)過程,當原串與模式串在某一位失配時,我們將模式串指針\(x\)通過\(next_x\)不斷回跳,直到能夠與原串匹配。

    • 類似的,當我們按照數位\(dp\)的階段,在後面加上\(0-9\)中的數字\(x\)時,我們同樣通過\(next_x\)匹配,再在尾部加上數字\(x\)

    • 因此我們可以設計出這樣的\(dp\)方程。令\(f_{i,j}\)表示前\(i\)位匹配到模式串的第\(j\)位的方案數,令\(pre_{i,0..9}\)

      表示通過\(next_i\)對於在第\(i\)位後加上數字\(0\leq x\leq 9\)匹配到模式串的第\(pre_{i,x}\)位。

    • \[ f_{i,pre_{j,x}}+=f_{i-1,j}\ (0\leq j<m,0\leq x\leq 9) \]

    • 這樣我們得到了一個時間復雜度為\(O(nm)\)優秀算法

    • 再看一眼範圍\(n\leq 10^9\)!!這樣我們就只能用加速線性遞推式的神器矩陣快速冪。將遞推式寫成矩陣的形式,用矩陣快速冪.....(感覺根本不會講)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=25;
int n,m,mod;
int a[MAXN];
int nxt[MAXN];
struct rec{
    int a[MAXN][MAXN];
    rec(){
        for (int i=0;i<=m;i++){
            for (int j=0;j<=m;j++) a[i][j]=0;
        }
    }
} A;
rec mul(rec a,rec b){
    rec c;
    for (int k=0;k<=m;k++){
        for (int i=0;i<=m;i++){
            for (int j=0;j<=m;j++){
                c.a[i][j]=(c.a[i][j]+a.a[i][k]*b.a[k][j])%mod;
            }
        }
    }
    return c;
}
rec mod_pow(rec a,int n){
    rec ans=a; n--;
    while (n){
        if (n&1) ans=mul(ans,a);
        a=mul(a,a);
        n>>=1;
    }
    return ans;
}
int main(){
    scanf("%d%d%d",&n,&m,&mod);
    for (int i=1;i<=m;i++){
        char c=getchar(); while (c<'0'||c>'9') c=getchar();
        a[i]=c-'0';
    }
//  cout<<"done"<<endl;
    nxt[1]=0;
    for (int i=2;i<=m;i++){
        int pre=nxt[i-1];
        while (pre>0&&a[pre+1]!=a[i]) pre=nxt[pre];
        if (a[pre+1]==a[i]) pre++;
        nxt[i]=pre;
    }
//  cout<<"done"<<endl;
    for (int i=0;i<m;i++){
        for (int j=0;j<=9;j++){
//          cout<<i<<" "<<j<<endl;
            int pre=i;
            while (pre>0&&a[pre+1]!=j) pre=nxt[pre];
            if (a[pre+1]==j) pre++;
            if (pre!=m) A.a[pre][i]=(A.a[pre][i]+1)%mod;
        }
    }
//  cout<<"done"<<endl;
    A=mod_pow(A,n);
    int ans=0;
    for (int i=0;i<m;i++) ans=(ans+A.a[i][0])%mod;
    printf("%d\n",ans);
    return 0;
}

bzoj1009-HNOI2008 GT考試 字符串dp+矩陣快速冪