状态压缩+矩阵乘法hdu-4332-Constructing Chimney

题目链接:

http://acm.hdu.edu.cn/showproblem.php?pid=4332

题目意思:

用1*1*2的长方体构造一个中间为空的底面3*3的立体烟囱。

解题思路:

实际上就是poj上这道题的升华版。推荐先做那道题。

只不过本题的每一层相当于poj上那题的每一行,此题层数很多,所以很直白的想到用矩阵快速幂加速。

这类型的矩阵乘法做的比较少。

用二维矩阵表示两层之间的转移关系,第一维表示上一层的状态,第二维表示下一层的状态,作为基矩阵。每次乘以它就相当于加了一层。状态图和矩阵转移如下,虽然很丑,但还看的清。

0表示当前层不放,那么它下面的一层肯定要为1(并且还是竖着的1),

1表示当前层放,可以是平着,也可以是竖着。(最后在统计最后一层平放的情况,最后一层竖着放肯定不行,超过了)

因为要多次调用基矩阵的偶次方,故预处理给保存起来。

状态压缩+矩阵乘法hdu-4332-Constructing Chimney_第1张图片

代码:

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<stack>
#include<list>
#include<queue>
#define eps 1e-6
#define INF 0x1f1f1f1f
#define PI acos(-1.0)
#define ll __int64
#define lson l,m,(rt<<1)
#define rson m+1,r,(rt<<1)|1
//#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

/*
freopen("data.in","r",stdin);
freopen("data.out","w",stdout);
*/

#define Maxn 300
#define M 1000000007

struct Mar
{
   int r,c;
   ll sa[Maxn][Maxn];

   void init(int a,int b) //矩阵的初始化
   {
      r=a,c=b;
      memset(sa,0,sizeof(sa));
   }

};
Mar mar[35]; //mar[][i][j] 表示从当前层i状态转到下一层的j状态的种数
//这种矩阵构造还是第一次见
ll ans[Maxn],tmp[Maxn];
int m;

Mar operator *(const Mar &a,const Mar &b)
{
   Mar cc;
   cc.init(a.r,b.c);

   for(int k=0;k<=a.c;k++) //注意要从0开始,因为0也是一种状态,纠结了好半天
   {
      for(int i=0;i<=a.r;i++)
      {
         if(a.sa[i][k]==0) //矩阵优化加速,把a矩阵的列或b矩阵的行 作为第一个循环
            continue;
         for(int j=0;j<=b.c;j++)
         {
            if(b.sa[k][j]==0)
               continue;
            cc.sa[i][j]=(cc.sa[i][j]+a.sa[i][k]*b.sa[k][j])%M;
         }
      }
   }
   return cc;
}
bool can[Maxn];

bool ok(int st) //是否有含有偶数个1 是的话可以横着放
{
   if(st==0)
      return true;
   int i=0;
   while((st&(1<<i))&&(i<8)) //找到第一个不是1的位置
      i++;
   if(i>=8)
      return true;
   for(int j=i+1;j<=i+8;j++) //循环起来,最多只需找8位
   {
      if(st&(1<<(j%8))) //两个两个一找
      {
         if(st&(1<<((j+1)%8)))
            j++;
         else
            return false;
         }
   }
   return true;
}
void iscan()
{
   memset(can,false,sizeof(can));
   for(int i=0;i<m;i++)
      if(ok(i)) //有偶数个连续的1
         can[i]=true;
   return ;
}

void Initba()
{//mar[0]应该是base mar[1]为base^2 mar[2]为base^4
   mar[0].init(m-1,m-1);
   for(int i=0;i<m;i++)
      for(int j=0;j<m;j++)
      {
         if((i|j)==m-1&&can[i&j])
            mar[0].sa[i][j]=1;
      }
   mar[0].sa[m-1][m-1]=2;// 此时下面一层可以有两种放法,题目中的第一个样例
   for(int i=1;i<32;i++) //先预处理起来,因为每次都要计算矩阵的话,很慢
      mar[i]=mar[i-1]*mar[i-1];
}
void mul(int x)
{
   memset(tmp,0,sizeof(tmp));
   for(int i=0;i<m;i++) //奇数的话 乘以一个
   {
      for(int j=0;j<m;j++)
         tmp[i]=(ans[j]*mar[x].sa[j][i]+tmp[i])%M;
   }
   for(int i=0;i<m;i++)
      ans[i]=tmp[i];
}

void quick(int n)
{
   memset(ans,0,sizeof(ans));
   ans[m-1]=1; //表示第一层必须全为1才可能在上面放,1是一个标识,表示之前的0层的数量
   n--; //第一层已经放好了
   for(int i=0;n&&i<32;i++)
      if(n&(1<<i))
         mul(i);
}

int main()
{
   m=1<<8;
  // printf("%d\n",m);
   iscan();
   Initba();
   int n,t;
   scanf("%d",&t);
   for(int ca=1;ca<=t;ca++)
   {
      scanf("%d",&n);
      quick(n);
      ll sum=0;
      for(int i=0;i<m;i++)
         if(can[i]) //最后一层可以平铺
         {
            sum=(sum+ans[i])%M;
            if(i==m-1) //最后一层有两种平铺法
               sum=(sum+ans[i])%M;
         }
      printf("Case %d: %I64d\n",ca,sum);
   }
   return 0;
}


你可能感兴趣的:(数学)