牛客-拆分(矩阵乘法)

题目大意

\(n\)个数,现在把它们划分成若干段,使存在一段的数的个数不为给出的\(k\)个数中的任意一个。问有多少种划分方式

\(n\le10^{18}\)

给出的\(k\)个数任意一个保证不超过\(100\)

分析

一个很明显的做法:先求出没有限制条件的总方案数

\(C^1_{n-1}+C^2_{n-1}+C^3_{n-1}+\dots+C^{n-1}_{n-1}+1\)

这个值就是\(2^{n-1}\)

接下来考虑减去特殊的情况

特殊情况即为:对于每个划分出来的段,个数都是给出的\(k\)个数之中的其中一个

考虑\(DP\)

\(f_i\)表示划分到第\(i\)个数,共有几种情况

转移方程即为:

\[f_i=\sum\limits_{j=1}^{k}{f_{i-t_j}} \]

看到\(n\)特别大,想到矩乘。

构建状态矩阵

\(\begin{bmatrix}f_i&f_{i-1}&f_{i-2}&\dots&f_{i-100}\end{bmatrix}\)

转移矩阵

第一列\(t_i\)位为\(1\),其余为\(0\)

其他列只有\(i-1\)位为\(1\),其余为\(0\)

#include <bits/stdc++.h>
#define int long long
using namespace std ;
int n , p , k ;
int a[ 110 ] ;
struct Matrix {
    int a[ 110 ][ 110 ] ;
    void clear () { memset ( a , 0 , sizeof ( a ) ) ; }
} ans , b ;
Matrix operator * ( Matrix A , Matrix B ) {
    Matrix C ;
    C.clear () ;
    for ( int i = 1 ; i <= 100 ; i ++ )
        for ( int j = 1 ; j <= 100 ; j ++ )
            for ( int k = 1 ; k <= 100 ; k ++ )
                C.a[ i ][ j ] = ( C.a[ i ][ j ] + A.a[ i ][ k ] * B.a[ k ][ j ] ) % p ;
    return C ;
}
inline int qpow ( int a , int b ) {
    int res = 1 ;
    while ( b ) {
        if ( b & 1 ) res = res * a % p ;
        a = a * a % p ;
        b >>= 1 ;
    }
    return res ;
}
inline void Matrix_pow ( int bs ) {
    while ( bs ) {
        if ( bs & 1 ) ans = ans * b ;
        b = b * b ;
        bs >>= 1 ;
    }
}
signed main () {
    ios :: sync_with_stdio ( false ) ;
    cin >> n >> p >> k ;
    b.clear () ;
    for ( int i = 1 ; i <= k ; i ++ ) {
        int t ; cin >> t ;
        b.a[ t ][ 1 ] = 1 ;
    }
    for ( int i = 2 ; i <= 100 ; i ++ ) b.a[ i - 1 ][ i ] = 1 ;
    ans.a[ 1 ][ 1 ] = 1 ;
    Matrix_pow ( n ) ;
    printf ( "%lld\n" , ( qpow ( 2 , n - 1 ) - ans.a[ 1 ][ 1 ] + p ) % p ) ;
    return 0 ;
}
posted @ 2020-09-10 19:11  hulean  阅读(350)  评论(0编辑  收藏  举报