您的位置:首页 > 其它

POJ2411(状压Dp,插头Dp,数学公式)

2017-04-12 17:12 330 查看
题目大意:有一个由n*m组成的地砖,现在拿1*2的地砖来铺,要求必须铺满。求总方案数(n,m<=11)。

这是一道状压Dp和插头Dp的经典题目。不过,或许很多人和我一样,看到这种题目后,都会想到搜索。可是搜索的时间复杂度是O(2^nm),肯定超时了。

【解法一:状压Dp】

首先设f[i][j]为前i行且第i行状态为j时的方案总数。那么f[i][j] = sum(f[i-1][j'] | j'能到达j);反过来,对于f[i][j],它可以向f[i+1][j'']贡献(其中j能到达j‘’)。

那样就可以设铺的为1,不铺的为0。拿1*2的地砖来铺,无非就是横铺或者竖铺。显然:如果第i行的第k位不铺,又由于必须铺满,所以第i+1行的第k为必须铺。也就是说,如果把状态j取反,二进制中为1的那一个位置就必须竖着铺。

所以,对于每一行的状态j,可以用DFS来算出j能到达的状态,则这个算法的时间复杂度为O(n*2^2m)。

小优化:若n和m都是奇数,则答案为0。

证明:因为 n,m为奇数,

         不妨设:n=2q-1,m=2p-1(q,p为正整数),

           所以:nm=(2q-1)(2p-1) = 4qp - 2q - 2p + 1

           又因为:4qp,2q,2p为偶数,

           所以:nm为奇数。

因为是拿1*2的地砖铺,且必须铺满。

所以nm是偶数,矛盾,正毕。

附上代码:

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;
#define Maxn 14

int sta[1<<Maxn];
long long f[Maxn][1<<Maxn];
int n,m,fp,p;

void dfs(int num,int p)
{
if(p+1==m)
{
sta[fp++] = num;
return;
}
dfs(num,p+1);//不铺

if((num&(1<<p)) || (num&(1<<(p+1)))) return;
dfs(num|(3<<p),p+1);//横着铺
}

int main()
{
while(scanf("%d%d",&n,&m) && n && m)
{
memset(f,0,sizeof(f));
if(n*m%2)//小优化
{
printf("0\n");
continue;
}
fp=0;
dfs(0,0);//预处理第一行
for(int i=0;i<fp;i++)
f[1][sta[i]] = 1;

p = 1<<m;
for(int i=1;i<n;i++)
for(int j=0;j<p;j++)//枚举当前状态j
{
fp = 0;
dfs(p-1-j,0);
for(int k=0;k<fp;k++)//枚举j能到达的状态
f[i+1][sta[k]] += f[i][j];
}
cout<<f
[p-1]<<endl;
}
return 0;
}


【解法二:插头Dp】:

设f[i][j][k]表示当前枚举到第i行第j列的地砖,且前面m个地砖(不包括(i,j))的状态为k(k用二进制表示,1表示有,0表示没有)。则有三种情况。

(1):不放,当且仅当k的第m位为1,才可以转移状态;

(2):往左放,当k的第m位为1,k的第1位为0且j不在最左时,可转移;

(3):往上放,当k的第m位为1且i不在最上面的一行时,转移;

我们可以根据上面的特点,用二进制表示出来。

因为每一个(i,j)只和上一次有关,则可以用滚动数组优化空间。

时间复杂度O(nm*2^m)。

程序:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
#define Maxn 14

long long f[2][1<<Maxn];//注意long long
int n,m,p,cur;

void change(int x,int y)//状态转移
{
if(y & (1<<m))
f[cur][y^(1<<m)] += f[cur^1][x];
}

int main()
{
while(scanf("%d%d",&n,&m) && n && m)
{
if(n*m%2)
{
printf("0\n");
continue;
}
if(n<m) swap(n,m);//小优化
memset(f,0,sizeof(f));

int p = 1<<m;
f[cur=0][p-1] = 1;

for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
{
cur^=1;
memset(f[cur],0,sizeof(f[cur]));
for(int k=0;k<p;k++)
{
change(k,k<<1);//不放 (第k位一定为1)
if(i && !(k&1<<m-1)) change(k,(k<<1) ^ (1<<m) ^ 1);
//往上放 (注意到“1<<m”,满足第k位一定为0,才是合法的,因为change函数进行转移时必须满足"y&(1<<m)",然后y|(1<<m)可以去掉它)
if(j && !(k&1)) change(k,(k<<1)^3);//往左放
}
}
printf("%lld\n",f[cur][p-1]);
}
return 0;
}


数学公式可以做:即

,但我们知道c++的三角函数和开方有硕大的浮点误差,或许产生精度问题。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  DP 解题报告