软件能力认证题---拼图(状态压缩DP+矩阵快速幂)

题意: 给定n*m的棋盘(1<=N<=10^15, 1<=M<=7),用L型骨牌(田字型任意去掉一个口)完全覆盖它,问有多少种解。

思路:m的范围只有1<=M<=7,显然状压DP。但是N的最大值到10^15,只能用快速幂了。

状态表示:0代表此处留空,1代表此处被填满。01序列压缩成一个int型来表示一行的填放情况。(例如:状态为4,则代表100,即第一列填满,第二第列三空)

边界条件:

其中,

t = 2^M

 代表将前i-1行填满,且第i行放置了状态s时的总方案数。

代表上一行原本放置了状态s2的前提下,当前行放置骨牌把上一行填满并使得当前行状态为s1的方案数。

那么问题来了,怎么取得矩阵D呢?

我枚举上一行状态s2,对每个s2进行dfs:依次扫描s2的每一位,如果有空位置,则尝试拜访某个方向的骨牌(显然共4种方向)。当把s2所有位置摆满以后,s1便产生了,那么让增加1
(初始为0).

有了矩阵D,天黑都不怕!

显然有

矩阵快速幂即可,最后输出  就是结果。

题目本身没多难,都怪我状压DP没学好,做了这题总算弄明白了一些误区。

明明还有好多事情要忙,但是看到这种东西就是想做,好久没有这种纯粹的感觉了。。

下面附上代码

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;

typedef long long LL;
const int mod = 1000000007;
const int maxn = 130;
int off[5]={0,1,1,2,2};
int d[maxn][maxn];
LL N;
int M; //N行 M列
int maxs; //总状态数 1<<M

inline void int2arr(int num,bool arr[])//数字转矩阵
{
  for(int i=0;i<M;++i,num>>=1)
    arr[M-i-1] = num&1;
}

inline int arr2int(bool arr[])//矩阵转数字
{
  int num = 0;
  for(int i=0;i<M;(num<<=1)|=arr[i++]);
  return num;
}

bool sbuf[2][10];
inline bool check(int cur,int type)//判断在cur位置是否可以放置type方向的骨牌
{
  if(type == 1)
    return sbuf[0][cur]==0 && sbuf[1][cur]==0 && cur+1<M && sbuf[1][cur+1]==0;

  if(type == 2)
    return sbuf[0][cur]==0 && sbuf[1][cur]==0 && cur-1>=0 && sbuf[1][cur-1]==0;

  if(type == 3)
    return sbuf[0][cur]==0 && sbuf[1][cur]==0 && cur+1<M && sbuf[0][cur+1]==0;

  if(type == 4)
    return sbuf[0][cur]==0 && cur+1<M && sbuf[0][cur+1]==0 && sbuf[1][cur+1]==0;

}

inline void putblock(int cur,int type,int cont)//在cur位置放置type方向的骨牌
{
  if(type == 1)
    sbuf[1][cur] = sbuf[1][cur+1] = cont;

  if(type == 2)
    sbuf[1][cur] = sbuf[1][cur-1] = cont;

  if(type == 3)
    sbuf[1][cur] = cont;

  if(type == 4)
    sbuf[1][cur+1] = cont;
}

void dfs(int cur)//dfs不多解释
{
  if(cur >= M)
  {
    ++d[arr2int(sbuf[1])][arr2int(sbuf[0])];
    return;
  }

  if(sbuf[0][cur]==1) dfs(cur+1);//如果当前位置已填,继续尝试下一列

  for(int i=1;i<=4;++i)
    if(check(cur,i))//如果当前位置可以放置i方向骨牌
    {
      putblock(cur,i,1);//尝试放置i方向骨牌
      dfs(cur+off[i]);//继续尝试后面列
      putblock(cur,i,0);//撤销放置i方向骨牌
    }
}

inline void getd()//计算矩阵D
{
  maxs = 1<<M;
  memset(d,0,sizeof(d));
  for(int i=0;i<maxs;++i)
      int2arr(i,sbuf[0]),
      dfs(0);

}

inline void matmult(int a[maxn][maxn],int b[maxn][maxn])//矩阵乘法a*=b
{
  static int c[maxn][maxn];
  memset(c,0,sizeof(c));
  for(int i=0;i<maxs;++i)
    for(int j=0;j<maxs;++j)
      for(int k=0;k<maxs;++k)
        c[i][j] = (c[i][j] + ((LL)a[i][k] * (LL)b[k][j]) % mod) % mod;

  memcpy(a,c,sizeof(c));
}

void matpower(int a[maxn][maxn],LL p)//矩阵快速幂a^p
{
  int ans[maxn][maxn];
  memset(ans,0,sizeof(ans));
  for(int i=0;i<maxs;++i) ans[i][i]=1;	//ans=1

  for(;p;p>>=1,matmult(a,a))
    if(p&1)
      matmult(ans,a);//ans*=a;
     //a*=a;
  memcpy(a,ans,sizeof(ans)); //return ans
}

int main()
{
  cin>>N>>M;
  getd();
  matpower(d,N);
  cout<< d[ maxs-1 ][ maxs-1 ] <<endl;
  return 0;
}

/*
各个方向的骨牌及其对应编号
 1  *
  **

 2  *
   **

 3  **
  *

 4  **
   *
*/
时间: 2024-10-25 17:01:49

软件能力认证题---拼图(状态压缩DP+矩阵快速幂)的相关文章

【BZOJ2004】公交线路(动态规划,状态压缩,矩阵快速幂)

[BZOJ2004]公交线路(动态规划,状态压缩,矩阵快速幂) 题面 BZOJ 题解 看到\(k,p\)这么小 不难想到状态压缩 看到\(n\)这么大,不难想到矩阵快速幂 那么,我们来考虑朴素的\(dp\) 设\(f[i][j]\)表示当前位置为\(i\),前面的\(P\)个位置的状态为\(j\) 其中,状态的含义是某个公交线路最后的停靠站 如果是最后的停靠站就是\(1\),否则是\(0\) 那么,任意状态中只存在\(k\)个\(1\) 并且表示\(i\)的二进制位一定是\(1\) 所以状态相当

POJ3420 Quad Tiling DP + 矩阵快速幂

题目大意是用1*2的骨牌堆积成4*N的矩形,一共有多少种方法,N不超过10^9. 这题和曾经在庞果网上做过的一道木块砌墙几乎一样.因为骨牌我们可以横着放,竖着放,我们假设以4为列,N为行这样去看,并且在骨牌覆盖的位置上置1,所以一共最多有16种状态.我们在第M行放骨牌的时候,第M+1行的状态也是有可能被改变的,设S(i,j)表示某一行状态为i时,将其铺满后下一行状态为j的方案书.考虑下如果我们让矩阵S和S相乘会有什么意义,考虑一下会发现S*S的意义当某行状态为i,接着其后面第2行的状态为j的可行

UVA 11651 - Krypton Number System(DP+矩阵快速幂)

UVA 11651 - Krypton Number System 题目链接 题意:给一个进制base,一个分数score求该进制下,有多少数满足一下条件: 1.没有连续数字 2.没有前导零 3.分数为score,分数的计算方式为相邻数字的平方差的和 思路:先从dp入手,dp[i][j]表示组成i,最后一个数字为j的种数,然后进行状态转移,推出前面一步能构成的状态,也就是到dp[(b - 1) * (b - 1)][x]. 然后可以发现后面的状态,都可以由前面这些状态统一转移出来,这样就可以利用

HDU 2294 Pendant (DP+矩阵快速幂降维)

HDU 2294 Pendant (DP+矩阵快速幂降维) ACM 题目地址:HDU 2294 Pendant 题意: 土豪给妹子做首饰,他有K种珍珠,每种N个,为了炫富,他每种珍珠都要用上.问他能做几种长度[1,N]的首饰. 分析: 1 ≤ N ≤ 1,000,000,000简直可怕. 首先想dp,很明显可以想到: dp[i][j] = (k-(j-1))*dp[i-1][j-1] + j*dp[i-1][j](dp[i][j]表示长度为i的并且有j种珍珠的垂饰有多少个) 然后遇到N太大的话,

HDU5863 cjj&#39;s string game(DP + 矩阵快速幂)

题目 Source http://acm.split.hdu.edu.cn/showproblem.php?pid=5863 Description cjj has k kinds of characters the number of which are infinite. He wants to build two strings with the characters. The lengths of the strings are both equal to n. cjj also def

hdu 4878 ZCC loves words(AC自动机+dp+矩阵快速幂+中国剩余定理)

hdu 4878 ZCC loves words(AC自动机+dp+矩阵快速幂+中国剩余定理) 题意:给出若干个模式串,总长度不超过40,对于某一个字符串,它有一个价值,对于这个价值的计算方法是这样的,设初始价值为V=1,假如这个串能匹配第k个模式串,则V=V*prime[k]*(i+len[k]),其中prime[k]表示第k个素数,i表示匹配的结束位置,len[k]表示第k个模式串的长度(注意,一个字符串可以多次匹配同意个模式串).问字符集为'A'-'Z'的字符,组成的所有的长为L的字符串,

POJ3735 Training little cats DP,矩阵快速幂,稀疏矩阵优化

题目大意是,n只猫,有k个动作让它们去完成,并且重复m次,动作主要有三类gi,ei,s i j,分别代表第i只猫获得一个花生,第i只猫吃掉它自己所有的花生,第i只和第j只猫交换彼此的花生.k,n不超过100,m不超过1000,000,000,计算出最后每只猫还剩下多少个花生. 我们假设一个n维向量P,每个分量的值代表这n只猫所拥有的花生数,那么对于gi操作其实就是在第i维分量上加上1:对于ei,那就是在第i维分量上乘以0,说到这里,有木有感觉这很像3D坐标转化中的平移矩阵和缩放矩阵?没错,就是这

HDU 5434 Peace small elephant 状压dp+矩阵快速幂

题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=5434 Peace small elephant Accepts: 38 Submissions: 108 Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others) 问题描述 小明很喜欢国际象棋,尤其喜欢国际象棋里面的大象(只要无阻挡能够斜着走任意格),但是他觉得国际象棋里的大象太凶残了,于是他

UVA-11625-Nice Prefixes (DP+矩阵快速幂)

题目(vjudge) 题面 题意: 你有K个字母,你需要用K个字母组成L长度的字符串,定义对于该字符串的任意前缀P 必须满足    ,输出方案数%1000000007的值. 思路: 首先可以想到一种简单的dp方程  dp [ len ] [ a ]  [b ]  表示当前字符串长度为len  个数为最多的字母有 a个  个数次多的有b个  (那么个数最少的有k-a-b个)状态数有 100*100,没办法矩阵快速幂加速dp. 考虑对于某个固定长度 len  如果确定 a,容易发现 b = (len