POJ 3420 Quad Tiling

题目链接:http://poj.org/problem?id=3420

题目大意:一个4*n的矩阵,用2*1的骨牌覆盖,有多少种方法mod M【n<=1e9 m<=1e5】

看到网上很多人的题解 都是构造一个16*16的矩阵。。

有点没想通为什么。。

我构造的是一个4*4的矩阵。

首先想好如何转移。。

假如当前计算到了dp[n]。。

首先可以由dp[n-1]转移来,方法只有1种。。

然后是dp[n-2],方法有4种。。

之后是dp[n-3]之前的状态。。

假如一个状态和n的距离为奇数,就只有2种转移方法。。

如果是偶数 就是3种转移方法。。

设sumodd为n-3之前 为奇数的状态个数和

sumevev为n-3之前 为哦偶数的状态个数和


总体的转移方程是 

dp[n]=dp[n-1]+4*dp[n-2]+2*sumeven[n-3]+3*sumodd[n-3]; 【n为奇数】

dp[n]=dp[n-1]+4*dp[n-2]+3*sumeven[n-3]+2*sumodd[n-3];【n为偶数】

初始状态 dp[1]=1 dp[2]=5;

因为一次要转移出一奇一偶两个状态 我们要推出dp[n+1]的公式

我们始终认定 n为 奇数

dp[n+1]=dp[n]+4*dp[n-1]+3*sumeven[n-2]+2*sumodd[n-2]

              =dp[n-1]+4*dp[n-2]+2*sumeven[n-3]+3*sumodd[n-3]+4*dp[n-1]+3*sumeven[n-2]+2*sumodd[n-2]   

             因为n为奇数 sumeven[n-2]==sumeven[n-3]   sumodd[n-2]==sumodd[n-3]+dp[n-2]

              =5*dp[n-1]+6*dp[n-2]+5*sumeven[n-3]+5*sumodd[n-3];

从这个转移方程就可以构造一个矩阵。。 

|  dp[n+1]            |                              |  dp[n-1]               |          |   5   1    1    0   |

|  dp[n]                 |                             |  dp[n-2]                |          |   6   4    0    1  |

|  sumeven[n-1]  |        =====         |  sumeven[n-3]   |   *     |   5   2    1    0  |

|  sumodd[n-1]   |                              |  sumodd[n-3]    |          |   5   3    0   1  |

最后看n是奇数还是偶数 决定输出第一项还是第二项

#include<set>
#include<cmath>
#include<stack>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<numeric>
#include<vector>
#include<ctime>
#include<queue>
#include<list>
#include<map>
#define pi acos(-1)
#define INF 0x7fffffff
#define clr(x)  memset(x,0,sizeof(x));
#define clrto(x,siz,y)  for(int xx=0;xx<=siz;xx++)  x[xx]=y;
#define clrset(x,siz)  for(int xx=0;xx<=siz;xx++)  x[xx]=xx;
#define clrvec(x,siz) for(int xx=0;x<=siz;xx++)  x[xx].clear();
#define fop   freopen("in.txt","r",stdin);freopen("out.txt","w",stdout);
#define myprogram By_135678942570
#define clrcpy(x,siz,y)  for(int xx=0;xx<siz;xx++)  x[xx]=y[xx];
using namespace std;
main()
{
     int n,m;
     while(scanf("%d%d",&n,&m)!=EOF)
     {
         if(n==0)
            break;
         long long maz[4][4]={5,1,1,0,6,4,0,1,5,2,1,0,5,3,0,1};
         long long int num[4]={5,1,1,0};
         int flag=n%2;
         if(n==1)
            printf("%d\n",1%m);
         else if(n==2)
            printf("%d\n",5%m);
         else
         {
            n-=2;
            n=n+1>>1;
            while(n)
            {
                if(n&1)
                {
                    long long temp[4]={0};
                    for(int i=0;i<4;i++)
                       for(int j=0;j<4;j++)
                          temp[i]=(temp[i]+num[j]*maz[j][i])%m;
                    for(int i=0;i<4;i++)  num[i]=temp[i];
                }
                long long temp[4][4]={0};
                for(int i=0;i<4;i++)
                  for(int j=0;j<4;j++)
                     for(int k=0;k<4;k++)
                        temp[i][j]=(temp[i][j]+maz[i][k]*maz[k][j])%m;
                for(int i=0;i<4;i++)
                   for(int j=0;j<4;j++)
                      maz[i][j]=temp[i][j];
                n>>=1;
            }
            if(flag==0)
            printf("%d\n",num[0]);
            else printf("%d\n",num[1]);
         }
     } 
     return 0;
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值