AcWing 291. 蒙德里安的梦想(状压dp)

题目链接:点击这里

在这里插入图片描述
在这里插入图片描述

思路:一列一列地考虑。先放横着的,再放竖着的。总方案数就等于只放横着的小方块的合法方案数。

如何判断当前方案是否合法?

所有剩余位置能否填充满竖着的小方块。也就是可以按列来看,每一列内部所有连续的空着的小方块需要是偶数个。

时间复杂度为 11 2 11 2 11 11*2^{11}*2^{11} ,时间还是有点紧张,不过可以AC:

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

using namespace std;
typedef long long ll;
const int N = 12, M = 1 << N;

int n, m;
ll f[N][M];
bool st[M];

int main()
{
    while(scanf("%d%d", &n, &m), n || m)
    {
        for(int i = 0; i < 1 << n; ++i)     //枚举小方块伸出来的所有状态是否合法
        {
            st[i] = true;       //先假设是合法的
            int cnt = 0;
            for(int j = 0; j < n; ++j)
            {
                if(i >> j & 1)
                {
                    if(cnt & 1) st[i] = false;  //连续的0是奇数就不合法
                    cnt = 0;
                }
                else
                    cnt++;
            }
            if(cnt & 1) st[i] = false;      //最后一块连续的0是奇数也不合法
        }
        
        memset(f, 0, sizeof f);
        f[0][0] = 1;    //第0列前面不会有小方块伸出来
        
        for(int i = 1; i <= m; ++i)     //枚举每一列
        {
            for(int j = 0; j < 1 << n; ++j)
            {
                for(int k = 0; k < 1 << n; ++k)
                {
                    if((j & k) == 0 && st[j | k])
                        f[i][j] += f[i-1][k];
                }
            }
        }
        
        printf("%lld\n", f[m][0]);
    }
    
    return 0;
}

优化:可以先预处理出前一列的所有合法的转移状态。

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

using namespace std;
typedef long long ll;
const int N = 12, M = 1 << N;

int n, m;
ll f[N][M];
bool st[M];

vector<int> state[M];

int main()
{
    while(scanf("%d%d", &n, &m), n || m)
    {
        for(int i = 0; i < 1 << n; ++i)     //枚举小方块伸出来的所有状态是否合法
        {
            st[i] = true;       //先假设是合法的
            int cnt = 0;
            for(int j = 0; j < n; ++j)
            {
                if(i >> j & 1)
                {
                    if(cnt & 1) st[i] = false;  //连续的0是奇数就不合法
                    cnt = 0;
                }
                else
                    cnt++;
            }
            if(cnt & 1) st[i] = false;      //最后一块连续的0是奇数也不合法
        }
        
        
        for(int i = 0; i < 1 << n; ++i)
        {
            state[i].clear();
            for(int j = 0; j < 1 << n; ++j)
            {
                if((i & j) == 0 && st[i | j])
                    state[i].push_back(j);
            }
        }
        
        memset(f, 0, sizeof f);
        f[0][0] = 1;    //第0列前面不会有小方块伸出来
        
        for(int i = 1; i <= m; ++i)     //枚举每一列
        {
            for(int j = 0; j < 1 << n; ++j)
            {
                for(auto k : state[j])
                {
                        f[i][j] += f[i-1][k];
                }
            }
        }
        
        printf("%lld\n", f[m][0]);
    }
    
    return 0;
}
发布了811 篇原创文章 · 获赞 127 · 访问量 14万+

猜你喜欢

转载自blog.csdn.net/qq_42815188/article/details/104881979