JZOJ4870. 涂色游戏

题目大意

给定一个 n×m 的网格图。有 p 种颜色可以用来涂在网格中。
定义一个方案是不单调的,当且仅当任意两列网格图中的颜色种类数加起来超过q种。
求不单调的方案数。

Data Constraint
n100,qp100,m109

题解

每一列的方案显然只与它前一列的方案有关。
f[i][j] 表示做完前 i 列,最后一列恰好选了j种颜色的方案数。
那么 f[i][k]+=f[i1][j]trans[j][k]
现在要处理的就是 trans[j][k] 了,这个可以预处理。
先枚举 j,k 再考虑 j,k 颜色集合的交集 x trans[j][k]=Cxj×Ckxpj×Tj,其中 Tj 表示一列恰好放 j 种颜色的方案。这个可以容斥一下:Tj=jnj1iTi×Cij
预处理出 trans 之后,再矩阵乘法一下即可。

SRC

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

#define N 100 + 10
typedef long long ll ;
const int MO = 998244353 ;
struct Matrix {
    ll Mat[N][N] ;
} trans , f , s , ret ;

ll fac[N] , _fac[N] , T[N] ;
int n , m , p , q ;
ll ans ;

Matrix operator * ( Matrix a , Matrix b ) {
    memset( ret.Mat , 0 , sizeof(ret.Mat) ) ;
    for (int i = 1 ; i <= p ; i ++ ) {
        for (int j = 1 ; j <= p ; j ++ ) {
            for (int k = 1 ; k <= p ; k ++ ) {
                ret.Mat[i][j] = (ret.Mat[i][j] + a.Mat[i][k] * b.Mat[k][j] % MO) % MO ;
            }
        }
    }
    return ret ;
}

ll Power( ll x , int k ) {
    ll s = 1 ;
    while ( k ) {
        if ( k & 1 ) s = s * x % MO ;
        x = x * x % MO ;
        k /= 2 ;
    }
    return s ;
}

ll C( int n , int m ) { return fac[n] * _fac[m] % MO * _fac[n-m] % MO ; }

Matrix PowerM( int k ) {
    for (int i = 1 ; i <= p ; i ++ ) s.Mat[i][i] = 1 ;
    while ( k ) {
        if ( k & 1 ) s = s * trans ;
        trans = trans * trans ;
        k /= 2 ;
    }
    return s ;
}

int main() {
    freopen( "color.in" , "r" , stdin ) ;
    freopen( "color.out" , "w" , stdout ) ;
    scanf( "%d%d%d%d" , &n , &m , &p , &q ) ;
    fac[0] = _fac[0] = 1 ;
    for (int i = 1 ; i <= max(n,p) ; i ++ ) {
        fac[i] = fac[i-1] * i % MO ;
        _fac[i] = Power( fac[i] , MO - 2 ) ;
    }
    for (int i = 1 ; i <= p ; i ++ ) {
        T[i] = Power( i , n ) ;
        ll sum = 0 ;
        for (int j = 1 ; j < i ; j ++ ) {
            sum = (sum + T[j] * C(i,j) % MO) % MO ;
        }
        T[i] = (T[i] - sum + MO) % MO ;
    }
    for (int i = 1 ; i <= min(n,p) ; i ++ ) {
        for (int j = 1 ; j <= min(n,p) ; j ++ ) {
            for (int x = max(i+j-p, 0) ; x <= min(i,j) ; x ++ ) {
                if ( i + j - x < q ) continue ;
                trans.Mat[i][j] = (trans.Mat[i][j] + C(i,x) * C(p-i,j-x) % MO * T[j] % MO) % MO ;
            }
        }
    }
    for (int i = 1 ; i <= p ; i ++ ) f.Mat[1][i] = C(p,i) * T[i] % MO ;
    f = f * PowerM( m - 1 ) ;
    for (int i = 1 ; i <= p ; i ++ ) ans = (ans + f.Mat[1][i]) % MO ;
    printf( "%lld\n" , ans ) ;
    return 0 ;
}

以上.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值