牛客-拆分(矩阵乘法)

题目大意

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

(nle10^{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=sumlimits_{j=1}^{k}{f_{i-t_j}} ]

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

构建状态矩阵

(egin{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
" , ( qpow ( 2 , n - 1 ) - ans.a[ 1 ][ 1 ] + p ) % p ) ;
    return 0 ;
}
原文地址:https://www.cnblogs.com/hulean/p/13647622.html