[poj 2411]Mondriaan's Dream[状态压缩DP]

来源:互联网 发布:超声成像算法导论 pdf 编辑:程序博客网 时间:2024/05/16 11:37

题意:

h*w的区域铺上1*2的砖,问有几种铺法.

思路:

(例程来源:

http://gisyhy.blog.163.com/blog/static/129390343200992441558735/

http://blog.sina.com.cn/s/blog_62ebd4410100h081.html

部分解读:

http://blog.sina.com.cn/s/blog_68b1a51b0100j91c.html#post)

解法一

一行一行铺,判断后一行能否从前一行转移而来.

具体见代码.

#include <iostream>#include <cstdio>#include <cstring>using namespace std;///624K   1954MS#define  N  11+1#define  S   (1<<12)#define  LL  long longLL dp[N][S];bool a[S];int n,Maxn,h,w;bool Ok(int i)//初始化第1行状态为i时是否容许{    for(int k = 0 ; k < w; )    {        if(i & (1<<k))        {            if(k == w-1 || !(i & (1<<(k+1))))///竖放只能靠脑补,初始化的时候只有横放                return false;            k+= 2;        }        else k ++;    }    return true;}/**1、如果第i行中有0,则第i-1行一定为1;2、如果第i行中为1的x列第i-1行为0,说明第i行肯定是竖着放的;(上一行0变1是脑补了)3、如果第i行中为1的x列第i-1行的该列也为1,可能性只有一个,   第i行是横放的,所以第i行的x+1列也必须为1,   又因为第i行的x+1列为1是因为横着放的,所以第i-1行的x+1列也必须为1。**/bool Pk(int i,int j)//判断i和j状态有无冲突{    int k;    for(k = 0; k < w; )    {        if((i & (1<<k)) == 0)        {            if((j & (1<<k)) == 0)//如果第i行为0,则第j行一定得为1                return false;            k ++;        }        else        {            if(j & (1<<k))                if(k == w-1 || ((i & (1<<(k+1))) && (j & (1<<(k+1)))) == 0)                    return false;                else k +=2;            else k++;        }    }    return true;}int main(){    while(scanf("%d %d",&h,&w),h && w)    {        int i,j,k;        if(h < w)//将状态数取小,优化处理            i = h ,h = w,w =i;        Maxn = (1<<w)-1;///闭区间        memset(dp,0,sizeof(dp));        for(i = 0 ; i <= Maxn; i++)//初始化第1行        {///就是合法的计数,不合法的留空            if(Ok(i))                dp[h][i] =1;///每种情况都试一遍,从下往上填的        }        for(k = h-1 ; k >=1; k --)//枚举第k行        {            for(i = 0 ; i <= Maxn; i++)//当前行的状态            {                for(j = 0; j <= Maxn ; j++) //前一行的状态                {                    if(Pk(i,j))///只是单纯的判断i状态能否由j状态转移而来                        dp[k][i] +=dp[k+1][j];                }            }        }///填了好多不必要的空格...        cout<<dp[1][Maxn]<<endl;///输出最后一行全部填满的情况    }    return 0;}

自己敲一遍:

加了两个优化:奇数面积直接输0;虚拟第0行,把第1行纳入check函数中(因为第一行就是check的特例~)

时间减少了1000MS....

#include <cstring>#include <cstdio>using namespace std;typedef long long ll;const int N = 12;const int S = 1 << 12;ll dp[N][S];int maxs,h,w;///548K  985MSbool ck(int i, int j){    for(int k=0;k<w; )    {        if(!(i & (1<<k)))        {///当前k位为0            if(!(j & (1<<k)))            {///前一行k位为0                return false;            }            k++;        }        else        {            if(j & (1<<k))            {///k 位都为1                if(k==w-1 || !(i & (1 << (k+1)) && j & (1 << (k+1))) )                    return false;///均为横放,下一位也都必须是1.当然首先得有下一位                else k += 2;///满足的话就越过一格            }            else k++;///竖放        }    }    return true;}int main(){    while(scanf("%d %d",&w,&h) && (h+w))    {        if((h*w)%2)        {            printf("0\n");            continue;        }        if(h<w)        {            int tmp = h;            h = w;            w = tmp;        }        maxs = 1<<w;        memset(dp,0,sizeof(dp));        dp[0][maxs-1] = 1;        ///虚拟第0行,这样就可以直接从第1行开始填数了.第0行只有一种全填满的方案        for(int i=1;i<=h;i++)        {            for(int j=0;j<maxs;j++)            {                for(int k=0;k<maxs;k++)                {                    if(ck(j,k))                        dp[i][j] += dp[i-1][k];                }            }        }        printf("%I64d\n",dp[h][maxs-1]);    }}

解法二(状态压缩DP+DFS记忆化搜索)

因为solve是递归的,所以是从最后一行往前填的.

state中的1表示(横放或竖放的)被占据的格子,它的前一行对应状态i,i中的1表示(横放或竖放)被占据的格子.

0表示其下state上的1为竖放(也就是说按照时间顺序,填i的时候该位置保留为0,填state的时候该0变为1).

这样dp[state][n]表示第n行为state状态,1~n-1行填满的方案数.


这样的话可以发现和解法一是完全等效的,也就可以看出其充分性.

#include <cstdio>#include <cstring>using namespace std;//360K   32MS__int64 dp[2049][12];//记忆化搜索__int64 num[12][12];//保存已经计算的结果不重复计算int n,m,power;inline bool exam(int x)//检查状态x是否满足全部是横放{    int i = 0;    while(i < m)    {        int sum = 0;        while((x & (1<<i)))        {            sum ++;    //找到连续的1            i++;        }        if(sum % 2) return(false);///遇到连续的1的个数为奇数,就判错        i++;    }    return(true);///全部检查完毕,都为偶,判对}__int64 solve(int state, int n){    __int64 sum = 0;    if(n == 1)//边界条件    {///表示第0行(虚拟)全填满1,state就表示公共的连续1        if(exam(state)) return(1);        return(0);    }    else    {        int last = ~state;        last = last & power;//找到state中0的位置标为1        if(dp[state][n] != -1) return(dp[state][n]);//已经搜索过        for(int i = 0; i <= power; i++)///遍历所有的方案        {            if((i & last) != last) continue;///state中0的位置上i中并不都为1,pass            int tmp = state & i;///state和i中都为1的位置标为1            ///至此,保证了当前行为0,前一行对应1;当前行为1且前一行也为1时,总是有连续的偶数个;            ///还剩下一种情况就是当前行为1而前一行为0,此时前一行的0脑补为1,认为是当前行的1竖放了!!!            if(exam(tmp)) sum += solve(i,n-1);//如果满足条件2        }        dp[state][n] = sum;//保存结果    }    return(sum);}int main(){    memset(num,-1,sizeof(num));    while(scanf("%d%d",&n,&m) == 2 && n && m)    {        if(n * m % 2)        {            printf("0\n");            continue;        }        if(m > n)        {            n = m ^ n;///保存m与n相异的位于n中            m = m ^ n;///将m中与n相异的位翻转而剩余不变,恰好是把m变成n            n = m ^ n;///把n的值再翻转为m存在n中        }        if(num[n][m] != -1)        {            printf("%I64d\n",num[n][m]);    //已经算过了            continue;        }        power = (1<<m) - 1;        memset(dp,-1,sizeof(dp));        num[n][m] = solve((1<<m)-1,n);        printf("%I64d\n",num[n][m]);    }    return(0);}
仍然是自己敲一遍~

#include <cstdio>#include <cstring>using namespace std;typedef long long ll;const int N = 12;ll dp[1<<(N-1)][N];///这里真的是太精打细算->11是奇数,而宽度又尽量小,所以实际宽度最大为10...ll num[N][N];int h,w,maxs;bool ck(int i){    int sum = 0;    for(int k=0;k<w; )    {        while(k<w && (i & (1<<k)))        {            sum++;            k++;        }        if(sum % 2)            return false;        sum = 0;        k++;///漏掉了哦~    }    return true;}ll dfs(int state, int i){    if(i==1)    {        if(ck(state))   return 1;        return 0;    }    if(dp[state][i]!=-1)    return dp[state][i];    ll ans = 0;    for(int s=0;s<maxs;s++)    {        int zero = ~state;        zero = zero & (maxs-1);        if((zero & s) != zero)    continue;///state中0不全对应s中1,不合法        int tmp = s & state;///common 1        if(ck(tmp))///even, valid            ans += dfs(s,i-1);    }    dp[state][i] = ans;    return ans;}int main(){    memset(num,-1,sizeof(num));    while(scanf("%d %d",&w,&h)==2 && (w+h))    {        if(w*h%2)        {            printf("0\n");            continue;        }        if(w>h)        {            h = w ^ h;            w = w ^ h;            h = w ^ h;        }        if(num[h][w]!=-1)        {            printf("%I64d\n",num[h][w]);            continue;        }        maxs = 1<<w;        memset(dp,-1,sizeof(dp));        num[h][w] = dfs(maxs-1,h);        printf("%I64d\n",num[h][w]);    }}

好吧,另附平生第一次打表→ →

#include <cstdio>using namespace std;typedef long long ll;const int N = 12;ll num[N][N] = {                {0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},                {1, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},                {1, 1, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1},                {1, 0, 3, 0, -1, -1, -1, -1, -1, -1, -1, -1},                {1, 1, 5, 11, 36, -1, -1, -1, -1, -1, -1, -1},                {1, 0, 8, 0, 95, 0, -1, -1, -1, -1, -1, -1},                {1, 1, 13, 41, 281, 1183, 6728, -1, -1, -1, -1, -1},                {1, 0, 21, 0, 781, 0, 31529, 0, -1, -1, -1, -1},                {1, 1, 34, 153, 2245, 14824, 167089, 1292697, 12988816, -1, -1, -1},                {1, 0, 55, 0, 6336, 0, 817991, 0, 108435745, 0, -1, -1},                {1, 1, 89, 571, 18061, 185921, 4213133, 53175517, 1031151241, 14479521761, 258584046368, -1},                {1, 0, 144, 0, 51205, 0, 21001799, 0, 8940739824, 0, 3852472573499, 0}            };int h,w;int main(){    while(scanf("%d %d",&h,&w)==2 && (h+w))    {        if(w>h)        {            h = w ^ h;            w = w ^ h;            h = w ^ h;        }    printf("%I64d\n",num[h][w]);    }}