最近学了状压dp,把之前未解决的题目捋一捋。
这是之前的一道题
今天蒜头君装修新家,给家里买了一种 1×2或2×1 的长方形(如图1)新瓷砖。蒜头君是个懂得审美的人,毕竟人生除了金钱,还有诗和远方。
这个时候蒜头君就在想,这种长方形的瓷砖铺到一个 10×10 10 \times 10 10×10 的地面上有多少种方案?(如图2:是 4×44 \times 44×4 地面的一种方案)
图1:
图2:
问10×10的地面用2×1的瓷砖铺能有多少方案。
我们直接一般化,有n×m的地面,求有多少种方案。
n×m的地面是有一道例题的 POJ2411,然后附上一篇自己认为还比较详细的题解
https://blog.csdn.net/u014634338/article/details/50015825
但是我也会讲解一点。
思路还是老思路,由前一行来推下一行。
首先我们选好状态的表示方法,1表示影响下一行,0表示不影响,为什么这样表示,因为这样可以很好的表示横着铺和竖着铺两种方案,1就表示这个格子是竖着铺的第一行,0表示这个格子可能是横着铺的中间一个,也可能是竖着铺的第二个格子,反正0就不影响,下一行不用考虑。
这是别人附的一张图,它是用0表示影响的,1表示不影响。
然后了解了状态表示之后,我们就可以推导了。
和很多题一样,对于新的一种状态,我们逐位考虑。
分两大类
①某一位是0。
这个上面也说过,它不影响下一行,可能是横这铺的,也可能是竖着铺的其中的第二个。
对于横着铺,我们要考虑旁边的格子是不是0,如果不是,直接就false了,这个状态不成立
对于竖着铺,首先我们判定它是竖着铺的方法是前一行的同一位是1,当前行的这一位是0,只有满足这两个条件就OK了
②第二类是某一位是1
这就说明它一定竖着铺,而且是第一个格子。
那么上一行就不能是1,就这一个条件。
由以上的思路,我们可以写出关键的函数。
下面是代码
#include
#include
#include
#include
#include
#include
#include
#define fori(l,r) for( int i = l ; i <= r ; i++ )
#define forj(l,r) for( int j = l ; j <= r ; j++ )
#define fork(l,r) for( int k = l ; k <= r ; k++ )
#define lef rt<<1
#define rig rt<<1|1
#define mid (l+r)>>1
#define mem(a,val) memset(a,val,sizeof a)
typedef long long ll;
using namespace std;
int r,c;
const int maxn = 1<<12;
ll dp[13][maxn];
bool initcheck( int x ) //检查第一行的状态
{
int cnt = c-1;
fori(1,c)
{
int curdigit = ( x>>cnt )&1;
if( curdigit == 0 )
{
if( i == c || ( ( x>>(cnt-1) )&1 ) == 1 )
return false;
i++;
cnt--;
}
cnt--;
}
return true;
}
bool judge( int cur,int pre )
{
int cnt = c-1;
fori(1,c)
{
if( ( cur>>cnt )&1 ) //当前行某一位为 1
{
if( ( pre>>cnt )&1 )
return false;
}
else
{
if( !( ( pre>>cnt)&1 ) ) //前面一行不影响,那么当前行要横着铺
{
if( i == c || ( pre>>(cnt-1)&1 ) || ( cur>>(cnt-1) )&1 )
return false;
i++; //横着铺有两格,下一个不用判断
cnt--;
}
}
cnt--;
}
return true;
}
int main()
{
while( scanf("%d %d",&r,&c) == 2 && r )
{
if( c > r )
swap(c,r);
mem(dp,0);
int right = 1<
当然,这个代码没加优化,但是也可以过。
其实有很多状态都是多余的。
我们用vector存一下每一行的状态,这样我们推导的时候,直接去vector里面找上一行的状态就行了,就是写起来有点麻烦。
但是优化的时间很多。存1200ms+到300多ms
说到最快的,当然是打表了,就直接跑一下所以情况,存到二维数组里面,直接输出。当然我觉得没必要这样。
#include
#include
#include
#include
#include
#include
#include
#define fori(l,r) for( int i = l ; i <= r ; i++ )
#define forj(l,r) for( int j = l ; j <= r ; j++ )
#define fork(l,r) for( int k = l ; k <= r ; k++ )
#define lef rt<<1
#define rig rt<<1|1
#define mid (l+r)>>1
#define mem(a,val) memset(a,val,sizeof a)
typedef long long ll;
using namespace std;
int r,c;
const int maxn = 1<<12;
ll dp[13][maxn];
bool vis[13][maxn];
vector v[13];
bool initcheck( int x ) //检查第一行的状态
{
int cnt = c-1;
fori(1,c)
{
int curdigit = ( x>>cnt )&1;
if( curdigit == 0 )
{
if( i == c || ( ( x>>(cnt-1) )&1 ) == 1 )
return false;
i++;
cnt--;
}
cnt--;
}
return true;
}
bool judge( int cur,int pre )
{
int cnt = c-1;
fori(1,c)
{
if( ( cur>>cnt )&1 ) //当前行某一位为 1
{
if( ( pre>>cnt )&1 )
return false;
}
else
{
if( !( ( pre>>cnt)&1 ) ) //前面一行不影响,那么当前行要横着铺
{
if( i == c || ( pre>>(cnt-1)&1 ) || ( cur>>(cnt-1) )&1 )
return false;
i++; //横着铺有两格,下一个不用判断
cnt--;
}
}
cnt--;
}
return true;
}
int main()
{
while( scanf("%d %d",&r,&c) == 2 && r )
{
if( c > r )
swap(c,r);
mem(dp,0);
mem(vis,false);
fori(1,r)
v[i].clear();
int right = 1<
那么对于前面的那道题,直接输入10 10就可以得到答案了。
哎,当时还是太菜