棋盘覆盖 状压DP+矩阵快速幂

题意:有一个m 行n 列的矩形方格棋盘,1 < = m< = 5,1=< n< =10^9,用1*2 的骨牌(可横放或竖放)完全覆盖,骨牌不能重叠,有多少种不同的覆盖的方法。你只需要求出覆盖方法总数 mod p 的值即可。

看到1e9立马知道快速幂DP或者数学方法,然后m<=5就状压吧

定义f[s][t]表示从s到t有多少种方案转移:则有f[s][t] = sigma(f[s][i] * f[i][t]) 所以可以用矩阵转移

最终答案就是f[(1<<m)-1][(1<<m)-1]

预处理一下两个状态能否转移到就可以矩阵加速DP了

Code:

#include <bits/stdc++.h>
using namespace std;
 
const int MAXS = 32;
 
int n, m, p;
 
struct Matrix
{
    int M[MAXS][MAXS];
    Matrix(){ memset(M, 0, sizeof M); }
    friend Matrix operator * (const Matrix &A, const Matrix &B)
    {
        Matrix C;
        for(int k = 0; k < (1<<m); k++)
            for(int i = 0; i < (1<<m); i++) if(A.M[i][k])
                for(int j = 0; j < (1<<m); j++) if(B.M[k][j])
                    C.M[i][j] = (1ll * A.M[i][k] * B.M[k][j] % p + C.M[i][j]) % p;
        return C;
    }
};
 
bool check(int s, int t)
{
    int left = 0;
    for(int i = 0; i < m; i++)
        if((s&(1<<i)) && (t&(1<<i)))
            (++left) %= 2;
        else
        {
            if(left) return 0;
            if(!(s&(1<<i)) && !(t&(1<<i)))
                return 0;
        }
    return left ? 0 : 1;
}
 
void Pow(Matrix &a, int b, Matrix &ret)
{
    ret = a;
    if(!b) return;
    while(b)
    {
        if(b & 1) ret = ret * a;
        a = a * a; b >>= 1;
    }
    return;
}
 
int main ()
{
    Matrix a, ans;
    scanf("%d%d%d", &n, &m, &p);
    for(int s = 0; s < (1<<m); s++)
        for(int t = 0; t < (1<<m); t++)
            a.M[s][t] = check(s, t);//预处理
    Pow(a, n-1, ans);
    printf("%d\n", ans.M[(1<<m)-1][(1<<m)-1]);
}

注意预处理s能否到达t时只能

(1)横着放在两列

(2)在t那一列竖着

因为只在s那一列放不属于本次转移

posted @ 2019-12-14 14:52  _Ark  阅读(198)  评论(0编辑  收藏  举报