现在的位置: 首页 > 综合 > 正文

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

2018年03月17日 ⁄ 综合 ⁄ 共 5220字 ⁄ 字号 评论关闭

题意:

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 long
LL 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  985MS
bool 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]);
    }
}


抱歉!评论已关闭.