多米诺覆盖

描述 Description
   JSOI2005夏令营的一天,JYY和lhc一起玩多米诺游戏。
 lhc提出一个令JYY抓头的问题:有一个m 行n 列的矩形方格棋盘,1<=m,n<=10^9,用1*2 的骨牌(可横放或竖放)完全覆盖,骨牌不能重叠,有多少种不同的覆盖的方法。JYY暗想,这题范围这么大,公式又那么难找,这怎么办呢?
 于是他要求lhc做出让步,lhc不得不做出让步,现在1<=n<=10^9,1<=m<=5。于是JYY高兴地在5分钟内解决了这个问题,不幸的是他把这个程序弄丢了,但他从不把做过的东西再做一遍,于是他把任务交给了你。
 请你帮助JYY编程解决:)
 lhc不想看到长串的高精度数,你只需要求出覆盖方法总数 mod p 的值即可。
输入格式 Input Format
  一行,三个整数数n,m,p,1<=n<=10^9,1<=m<=5,1<=p<=10000。
输出格式 Output Format
 一个整数,总数 mod p 的结果。

样例输入 Sample Input

7 2 10

样例输出 Sample Output

1

来源 Source
 JSOI06夏令营测试题增强版

原来的夏令营测试题的n的范围貌似只有1000,只要for一遍DP就行了。这题n这么大,加个矩阵快速幂就可以了。

我的思想是这样的:把每一行的状态表示成一个二进制数,因为m只有5,所以状态数最多只有64种。每一种状态中,0表示没有被覆盖,1表示被覆盖。考虑当前行在放置骨牌后会转移到下一行的什么样的状态,这样就可以得到一个矩阵。因为要覆盖满,所以把第0行的状态当做都被覆盖,第n+1行的状态当做都没有被覆盖,求出矩阵的n+1次幂,输出最后状态为零的情况数即可。

 

AC CODE

 

program fu_1380;
var t,tt,tmp:Array[0..64,0..64] of longint;
    d,a,b:array[0..64] of longint;
    i,j,n,m,p,k:longint;
//============================================================================
procedure ins;
var k,j:longint;
begin
  k:=0; for j:=1 to m do k:=k+b[j]*d[j];
  inc(T[i,k]); t[i,k]:=t[i,k] mod p;
end;
//============================================================================
procedure dfs(now,step:longint);
begin
  if now=m+1 then ins else
  if a[now]=1 then dfs(now+1,1) else    //当前格被覆盖,则下一行的对应格通过转移就一定不会被覆盖。
  begin
    b[now]:=1; dfs(now+1,1); b[now]:=0;    //竖放,占两行。
    if a[now+1]=0 then dfs(now+2,2);    //有连续两个空格就须考虑平放在一行的情况。则下一行这两格都不会被覆盖。
  end;
end;
//============================================================================
procedure quick_power(x:longint);    //矩阵快速幂。
var i,j,k:longint;
begin
  if x=1 then
  begin
    for i:=0 to d[m+1]-1 do
      for j:=0 to d[m+1]-1 do tt[i,j]:=t[i,j];
    exit;
  end; quick_power(x div 2);
  for i:=0 to d[m+1]-1 do
    for j:=0 to d[m+1]-1 do
      for k:=0 to d[m+1]-1 do
        tmp[i,j]:=(tmp[i,j]+tt[i,k]*tt[k,j]) mod p;
  for i:=0 to d[m+1]-1 do
    for j:=0 to d[m+1]-1 do
    begin
      tt[i,j]:=tmp[i,j]; tmp[i,j]:=0;
    end;
  if x mod 2=1 then
  begin
    for i:=0 to d[m+1]-1 do
      for j:=0 to d[m+1]-1 do
        for k:=0 to d[m+1]-1 do
          tmp[i,j]:=(tmp[i,j]+tt[i,k]*t[k,j]) mod p;
    for i:=0 to d[m+1]-1 do
      for j:=0 to d[m+1]-1 do
      begin
        tt[i,j]:=tmp[i,j]; tmp[i,j]:=0;
      end;
  end;
end;
//============================================================================
begin
  readln(n,m,p); d[1]:=1;
  for i:=2 to m+1 do d[i]:=(d[i-1]*2) mod p;  //预处理2的k次幂。
  a[1]:=-1; a[m+1]:=-maxlongint;
  for i:=0 to d[m+1]-1 do
  begin
    inc(a[1]); k:=1;

    while a[k]>1 do
      begin a[k]:=0; inc(a[k+1]); inc(k); end;      //枚举当前行的状态。
    for k:=1 to m do b[i]:=0;
    dfs(1,1);    //深搜枚举可以转移到的状态。
  end; quick_power(n+1);    //矩阵快速幂。
  writeln(tt[0,d[m+1]-1]);    //本来是用第一行的状态乘以转移矩阵的n+1次幂的。得到最后一行的状态,再输出状态为0的情况数,但是观察发现就是tt[0,d[m+1]-1]...
end.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值