[POJ 3420]Quad Tiling(状压DP+矩阵乘法)

题目链接

http://poj.org/problem?id=3420

题目大意

给你一个 4n 大小的棋盘,要你在上面用若干个 12 大小的方块填满,两个方块之间互相不能重叠。问填满方块的方案数

思路

考虑 n 比较小的情况。

f[i][S] 表示当前dp到第 i 列,上一列格子是否填满的二进制状态为 S 的方案数。那么可以假设存在第0列,该列状态为 (1111)2 ,则初始时 f[0][(1111)2]=1 ,最终的答案为 f[n+1][(1111)2]
DP方程为

f[i][S]=SSf[i1][S]

这个 S 之间的转移,我们可以通过手工人肉出来,也很容易发现其中的规律
[POJ 3420]Quad Tiling(状压DP+矩阵乘法)_第1张图片
图片来自http://www.hankcs.com/program/algorithm/poj-3420-quad-tiling.html

可以发现,第 i1 列的 S 里若存在空格(0),则第 i 列的 S 的这几位必须为1(在这几个位置横着放上方块),因此可以通过枚举 S 求出 S 。但是有特例,若第 i1 列的 S 里存在连续的2个空格,则可以在这个位置竖着放上方块,第 i 列的 S 的这几位就变成了0,这些特殊情况可以单独打表。

然后发现题目的 n 非常大,上面的DP可以把二进制状态看成是有向图里的结点,转移看成是有向边,这就变成了一个很经典的线性代数问题:走 n 个单位时间,从 (1111)2 结点走到 (0000)2 结点需要多长时间,直接上矩阵快速幂即可

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 110

using namespace std;

typedef long long int LL;

LL MOD;

struct Matrix
{
    int n,m;
    LL num[MAXN][MAXN];
    Matrix()
    {
        n=m=0;
        memset(num,0,sizeof(num));
    }
}B,A,mp,one;

Matrix operator*(Matrix a,Matrix b)
{
    Matrix ans;
    ans.n=a.n,ans.m=b.m;
    for(int k=1;k<=a.m;k++)
        for(int i=1;i<=ans.n;i++)
            for(int j=1;j<=ans.m;j++)
                ans.num[i][j]=(ans.num[i][j]+(a.num[i][k]*b.num[k][j])%MOD)%MOD;
    return ans;
}

Matrix fastPow(Matrix base,int pow)
{
    Matrix ans=one;
    while(pow)
    {
        if(pow&1) ans=ans*base;
        base=base*base;
        pow>>=1;
    }
    return ans;
}

int n;

int main()
{
    for(int S=0;S<16;S++)
    {
        int newS=0;
        for(int i=0;i<4;i++)
        {
            if(S&(1<<i)) continue;
            newS|=1<<i;
        }
        mp.num[S+1][newS+1]++;
    }
    mp.num[13][1]++;
    mp.num[10][1]++;
    mp.num[4][1]++;
    mp.num[1][4]++;
    mp.num[1][10]++;
    mp.num[1][13]++;
    mp.num[1][1]++;
    mp.num[2][9]++;
    mp.num[2][3]++;
    mp.num[9][2]++;
    mp.num[3][2]++;
    one.n=one.m=mp.n=mp.m=16;
    B.n=1,B.m=16;
    B.num[1][1]=1;
    for(int i=1;i<=16;i++)
        one.num[i][i]=1;
    while(scanf("%d%lld",&n,&MOD)!=EOF&&!(!n&&!MOD))
    {
        B.n=1,B.m=16;
        B.num[1][16]=1;
        B=fastPow(mp,n);
        /*for(int i=1;i<=16;i++) { for(int j=1;j<=16;j++) printf("%lld ",mp.num[i][j]); printf("\n"); }*/
        printf("%lld\n",B.num[1][1]);
    }
    return 0;
}

你可能感兴趣的:([POJ 3420]Quad Tiling(状压DP+矩阵乘法))