[DP+KMP+矩阵快速幂优化] BZOJ 1009

3 篇文章 0 订阅

题意

给定长度为m的数字串s,求不包含子串s的长度为n的数字串的数量


思路

状态转移方程

dp[i][j]=Σ dp[i1][k]a[k][j]   (0<k<m) d p [ i ] [ j ] = Σ   d p [ i − 1 ] [ k ] ∗ a [ k ] [ j ]       ( 0 < k < m )
dp[i][j] d p [ i ] [ j ] 代表当前长度为i的字符串, 后j位和目的串的前j位匹配,这样的串有多少种
a[k][j] a [ k ] [ j ] 代表当前字符串后k位与目的串匹配, 再加上新的一位使字符串的后缀变成j个,这样的状态转移有多少种
所以 i1 i − 1 的串后缀为 k k dp[i1][k] d p [ i − 1 ] [ k ] 种,乘上转移的种类数,就是长度为 i i 的串有多少种了


矩阵快速幂优化dp

由上式可得
dp[n][j]=Σ dp[0][k](a[k][j])n d p [ n ] [ j ] = Σ   d p [ 0 ] [ k ] ∗ ( a [ k ] [ j ] ) n
因此求结果我们直接用快速幂计算  a    a   矩阵的n次方即可
而初始条件 dp[0][0]=1 d p [ 0 ] [ 0 ] = 1 除此之外 dp[0][x]=0 d p [ 0 ] [ x ] = 0
所以上式变成 dp[n][j]=dp[0][0](a[0][j])n d p [ n ] [ j ] = d p [ 0 ] [ 0 ] ∗ ( a [ 0 ] [ j ] ) n
最终结果对所有的j求和,表示长度为n的字符串后缀为0~m-1的种类数,即我们要求的可行的字符串数


KMP

现在解决转移矩阵 a a 的求法
首先看一个a的样例
s=12312 s = 12312
9 1 0 0 0 0
8 1 1 0 0 0
8 1 0 1 0 0
9 0 0 0 1 0
8 1 0 0 0 1
a[0][0]=9 a [ 0 ] [ 0 ] = 9 代表当前匹配0位,转移到匹配0位有9种,即除了1以外的所有数字
a[1][0]=8 a [ 1 ] [ 0 ] = 8 从1位到0位,即除了1和2以外的所有数. ( +1匹配到1, +2匹配到2 )
a[0][1]=1 a [ 0 ] [ 1 ] = 1 从0位到1位, 只能是1
求法见代码(kmp不熟)


代码

#include <algorithm>
#include <bits/stdc++.h>
#include <cstdio>
#include <cstring>
#include <iostream>

using namespace std;
typedef long long ll;

const int INF = 0x7f7f7f7f;
const int maxn = 100 + 10;
int MOD;

int n, m; //需要长度,给定串长度
char s[ maxn ];
int nex[ maxn ];

struct MAtrix {
    int xx[ 22 ][ 22 ];
    int *operator[] ( int x ) { return xx[ x ]; }
} a, b;

void operator*= ( MAtrix &x, MAtrix &y ) {
    MAtrix z;
    memset ( &z, 0, sizeof ( z ) );

    for ( int i = 0; i < m; ++i )
        for ( int j = 0; j < m; ++j )
            for ( int k = 0; k < m; ++k )
                z[ i ][ j ] = ( z[ i ][ j ] + x[ i ][ k ] * y[ k ][ j ] ) % MOD;
    x = z;
}

void kmp () {
    int fix = 0;
    for ( int i = 2; i <= m; ++i ) {
        while ( fix && s[ fix + 1 ] != s[ i ] ) //匹配失败,回到前面可以再匹配的位置
            fix = nex[ fix ];
        if ( s[ fix + 1 ] == s[ i ] ) //匹配成功,fix往下走
            ++fix;
        nex[ i ] = fix;
    }
    for ( int i = 0; i < m; ++i ) //枚举i,当前末尾匹配了几位,求b[ i ][ k ];
        for ( char j = '0'; j <= '9'; ++j ) {
            fix = i;
            while ( fix && s[ fix + 1 ] != j ) //匹配失败,转到下一个匹配的位置
                fix = nex[ fix ];
            if ( j == s[ fix + 1 ] ) //匹配成功,从i到当前可以匹配的位置(默认为i+1),可能的路径++
                b[ i ][ fix + 1 ]++;
            else
                b[ i ][ 0 ]++; //直到fix走到最头也无法匹配成功,只能转移到0位匹配
        }
}

void quick_pow ( int x ) {
    while ( x ) {
        if ( x & 1 )
            a *= b;
        b *= b;
        x >>= 1;
    }
}

int main () {
#ifdef LOCAL
    freopen ( "in", "r", stdin );
    // freopen("out","w",stdout);
#endif
    scanf ( "%d%d%d", &n, &m, &MOD );
    scanf ( "%s", s + 1 );
    kmp ();

    for ( int i = 0; i < m; ++i )
        a[ i ][ i ] = 1; //单位矩阵,求b矩阵的n次幂
    quick_pow ( n );     // b ^ n;

    int ans = 0;
    for ( int i = 0; i < m; ++i )
        ans = ( ans + a[ 0 ][ i ] ) % MOD;//最终结果
    printf ( "%d\n", ans );

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值