hihocoder 1162 : 骨牌覆盖问题·三 矩阵快速幂

描述

前两周里,我们讲解了2xN,3xN骨牌覆盖的问题,并且引入了两种不同的递推方法。
这一次我们再加强一次题目,对于给定的K和N,我们需要去求KxN棋盘的覆盖方案数。

提示:KxN骨牌覆盖

输入

第1行:2个整数N。表示棋盘宽度为k,长度为N。2≤K≤7,1≤N≤100,000,000

输出

第1行:1个整数,表示覆盖方案数 MOD 12357

Sample Input
2 62247088
Sample Output
1399

在2xN的骨牌问题中,我们有答案的递推序列。f[n] = f[n-1]+f[n-2]。
事实上在处理3xN的问题中,也有部分选手推导出了答案的递推序列。
那么对于4xN,5xN,是否也存在答案的递推序列呢?有兴趣的选手不妨尝试推导一下。

在上一期,也就是3xN问题中,我们介绍了根据状态来递推的方法。这种方法显然是通用性最好的,可以用来解决任何K值的覆盖。
对于任意一个K值,我们每一行拥有的状态数量为2^K种。
在K=3时,我们是通过手动来枚举的8种状态之间的递推关系。
当K=4或者更大的时候,再通过手动枚举就显得不那么科学了,此时我们需要考虑如何用程序生成我们所需要的状态转移矩阵。

让我们再回头看看我们上一期提示里面放置骨牌的约定:
假设我们正在放置第i行的骨牌,那么会有下面3种方式:

灰色表示已经有的骨牌,绿色表示新放置的骨牌。
每一种放置方法解释如下,假设当第i行的状态为x,第i-1行的状态为y:

  • 第i行不放置,则前一行必须有放置的骨牌。x对应二进制位为0,y对应二进制位为1。
  • 第i行竖放骨牌,则前一行必须为空。x对应二进制位为1,y对应二进制位为0。
  • 第i行横向骨牌,则前一行必须两个位置均有骨牌,否则会产生空位。x对应二进制位为1,y对应二进制位为1。
    既然有对应的二进制描述,那么上面三种方法就可以用程序语言解释为:
  • 第i行不放置:new_x = x << 1, new_y = (y << 1) + 1; 列数+1
  • 第i行竖放骨牌:new_x = (x << 1) + 1, new_y = y << 1; 列数+1
  • 第i行横向骨牌:new x = (x << 2) + 3, new_y = (y << 2) + 3; 列数+2
    通过迭代去枚举3种放置方法,当总的列数等于K时,此时的x便可由y转移过来。那么我们可以得到枚举放置的伪代码:
    DFS(x, y, col):
    If col == K
    d[y][x] = 1
    Return ;
    End
    DFS(x << 1, (y << 1) + 1, col + 1);
    DFS((x << 1) + 1, y << 1, col + 1);
    If col + 2 <= K
    DFS( (x << 2) + 3, (y << 2) + 3, col + 2 )
    End

    当我们得到对应的矩阵之后,剩下需要做的也就和上一期相同了。

    在某些题目中有可能会出现,N很小,K很大的情况。比如N=20,K=14这样的情况。
    考虑到N很小,我们可以不使用矩阵乘法,而直接采用f[i-1]到f[i]行的递推。时间复杂度也就转化为2^(2k)*N。
    但是状态数量为2^14,也就是16384种。若采用转移矩阵,肯定是无法储存的。而实际情况是在转移矩阵中1的数量并不多,所以我们可以考虑存储为(y,x)这样的二元组。在转移过程中只枚举合法的转移即可。
    若K再更大一点,比如K=20,产生的状态有可能连开数组存储都很吃力。这个时候我们也可以考虑在计算每一行时,直接通过dfs来进行转移,不储存转移关系。用时间来换取空间。



    #include <iostream>
    #include<bits/stdc++.h>
    #define ll long long
    
    using namespace std;
    
    typedef vector<ll> vec;
    typedef vector<vec> mat;
    const ll MOD=12357;
    
    mat mul(mat &A,mat &B)
    {
        mat C(A.size(),vec(B[0].size()));
        for(int i=0;i<A.size();i++)
            for(int k=0;k<B.size();k++)
                for(int j=0;j<B[0].size();j++)
                    C[i][j]=(C[i][j]+A[i][k]*B[k][j])%MOD;
        return C;
    }
    
    mat pow(mat A, ll n)
    {
        mat B(A.size(),vec(A.size()));
        for(int i=0;i<A.size();i++) B[i][i]=1;
        while(n>0)
        {
            if(n&1) B=mul(B,A);
            A=mul(A,A);
            n>>=1;
        }
        return B;
    }
    int k;
    ll d[220][220];
    void dfs(int x,int y,int col)
    {
        if(col==k)
        {
            d[y][x]=1;
            return;
        }
        dfs(x<<1,(y<<1)+1,col+1);
        dfs((x<<1)+1,y<<1,col+1);
        if(col+2<=k)    dfs((x<<2)+3,(y<<2)+3,col+2);
    }
    
    
    
    int main()
    {
        ll n;
        while(~scanf("%d%lld",&k,&n))
        {
            dfs(0,0,0);
            ll t=1<<k;
            mat A(t,vec(t));
            for(int i=0;i<t;i++)
                for(int j=0;j<t;j++)
                    A[i][j]=d[i][j];
            A=pow(A,n);
            mat B(1,vec(t));
            B[0][t-1]=1;
            B=mul(B,A);
            printf("%lld\n",B[0][t-1]);
        }
    }
    //2 62247088
    

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值