POJ2411 状态压缩DP(矩阵填块)

题目:
给你nm(1<=n,m<=11)的方格矩阵,要求用12的多米诺骨牌去填充,问有多少种填充方法。

分析:
首先我们定义如下这种填充表示方式:如果一个骨牌是横着放的,那么它所在的两个方格都填充1.如果它是竖着放的,那么它所在的两个格子中,上面的那个填0,下面的这个填1.如下图所示:

在这里插入图片描述

该矩阵的骨牌摆放方法和该矩阵的二进制表示法是一一对应的。对于任意连续的两行i-1和i行来说,第i-1行的二进制表示和i行的二进制表示有一个兼容的关系。

用pre表示前一行的二进制形式,now表示后一行的二进制形式,c表示当前操作的列号。c,pre,now初值都为0.由于是从左边位模拟到最右边的位,所以对应3种操作的结果为:

  • 1,当前位置竖着放置,那个当前位置就为0,那个上一行的这一列位置上的一定是1,(下一行这一列就不用放了),
    继续向右移动,now<<1+0, pre<<1+1, c+1;

  • 2当前位置横着放,那个那么当前位置和右边一个位置就为11(十进制就是3),上一行对应位置一定也是11,那么c(列就可以向右移动两位了), c+2, now<<2+3, pre<<2+3;

  • 3当前位置不放,(也就是这一列的这两行放着一个竖着的小块)即上一行是0,这一行是1,c+1, pre<<1, now<<1+1;

上面的过程就是模拟出所有的相邻两行可以以什么样的(相互兼容的)样式放在两行中

//path[i][0]---表示第i种两行相互兼容的方式中前一行值是多少(11111111这种形式转化为了十进制了)
//path[i][1]--表示第i中两行相互兼容的方式中当前行的二进制形式对应的值是多少,
	void get(int c,int pre,int now)//c表示当前列号,得到具有m列的矩阵的所有对应兼容方式存入path中
	{
	    if(c>m)return ;
	    else if(c==m)
	    {
	        path[w][0]=pre;
	        path[w++][1]=now;
	        return;
	    }
	    get(c+1,(pre<<1)|1,now<<1);         //竖着放
	    get(c+1,(pre<<1),(now<<1)|1);      //不放
	    get(c+2,(pre<<2)|3,(now<<2)|3);		//横着放,
	}

d[i][num]----表示第i行对应二进制形式值为num时,有多少种方法,

然后就是遍历,

 d[0][(1<<m)-1]=1;//假想的第0行且二进制形式为全1时 出现1次
  for(int i=0; i<n; i++)//共n行
       for(int j=0; j<w; j++)//每行有w种对应的兼容方式
       {
           d[i+1][path[j][1]] +=d[i][path[j][0]];//path[j][0]与path[j][1]分别指兼容的两个二进制
           //d[i][x]表示第i行且该行的二进制表示为X时,共有多少种摆放骨牌的方式。
       }

总的代码:

	#include<cstdio>
	#include<cstring>
	#include<algorithm>
	using namespace std;
	int n,m,w;
	const int maxn =15;
	long long d[maxn][1<<15];
	int path[5000000][2];//path[10][0]表示第11种(从0计数)兼容方式的前一行二进制形式对应的的值
	//path[10][1]表示第11种(从0计数)兼容方式的后一行二进制形式对应的的值
	//一共可能有(1<<11)*(1<<11)种兼容方式
	 
	void get(int c,int pre,int now)//得到具有m列的矩阵的所有对应兼容方式存入path中
	{
	    if(c>m)return ;
	    else if(c==m)
	    {
	        path[w][0]=pre;
	        path[w++][1]=now;
	        return;
	    }
	    get(c+1,(pre<<1)|1,now<<1);
	    get(c+1,(pre<<1),(now<<1)|1);
	    get(c+2,(pre<<2)|3,(now<<2)|3);
	}
	 
	int main()
	{
	 
	    while(scanf("%d%d",&n,&m)==2&&n&&m)
	    {
	        w=0;//兼容方式总数
	        if(m>n)swap(n,m);//始终保持n为行,m为列数,且m较小
	        get(0,0,0);//得到所有兼容方式
	        memset(d,0,sizeof(d));
	        d[0][(1<<m)-1]=1;//假想的第0行且二进制形式为全1时 出现1次
	        for(int i=0; i<n; i++)//共n行
	            for(int j=0; j<w; j++)//每行有w种对应的兼容方式
	            {
	                d[i+1][path[j][1]] +=d[i][path[j][0]];//path[j][0]与path[j][1]分别指兼容的两个二进制
	                //d[i][x]表示第i行且该行的二进制表示为X时,共有多少种摆放骨牌的方式。
	            }
	        printf("%I64d\n",d[n][(1<<m)-1]);
	    }
	    return 0;
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值