轮廓线dp

第一次接触轮廓线dp,在网上找一些资料感觉介绍很不清楚,看了这篇博客后清晰了一些,主要是根据这道题目Mondriaan’s Dream讲解一下我的理解

图1在这里插入图片描述

  • 首先什么是轮廓线,从我查阅的资料来看是未确定的状态和已经确定状态的交界处即使轮廓线,比如上图中格子中的数值为1即是已经确定了状态,从上到下,从左到右依次决定放不放1*2的骨牌都不影响到这些数值为1的格子,而对于K4,K3,K2,K1,K0,这些方格状态都还未确定,后续的操作都会影响到这些格子的状态,所以轮廓线以上的状态都是已经确定的,而轮廓线以下包括轮廓线本身的状态都是未确定的,所以我们可以进行状态压缩,使用一个int型变量s保存轮廓线的状态。
  • 然后是如何表示状态s,可以使用上图K4K3K2K1K0二进制串表示状态s,也可以使用下面这种从左向右,或者从右到左的表示形式,只要状态转移是对应的转移方式就是。向上图的状态s转移需要左移一位,丢弃高位K4,并补充低位K0,而下面这种方式则不需要移位操作,所以看网上代码的状态转移过程有的进行移位,有的没有,给我整懵逼了。后面就用上图进行讲解了

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

  • 理解上面的就分类讨论了,先判断轮廓线状态s的对应(i,j)位置上面的状态是0还是1,如果是0(情况1)则必须竖放,因为只有当前位置可以影响到上面位置,要满足题目填满的要求只能竖放,如果上面位置是1,则考虑左边的位置是0还是1,如果是0(情况2),则可以向左横着放置,也可以选择不放,因为第i+1行可以竖放填满第i行的空格,然后就是情况3了,只能选择不放。
    在这里插入图片描述

首先这道题可以使用常规的dp,枚举第i-1行的状态和第i行的状态进行状态转移。

#include<iostream>
#include<cstring>
using namespace std;
const int ms = 1<<12;
long long dp[2][ms];
bool legal[ms];
int n, m;
bool ck(int i,int j){
    bool f = true;
    for(int k=0;k<m;){
        if(!((j>>k)&1)){
            if((i>>k)&1){
                k+=1;
            }else{
                f = false;
                break;
            }
        }else{
            if(!((i>>k)&1)){
                k+=1;
            }else{
                if((k+1)<m && (i>>(k+1)&1) && (j>>(k+1))&1){
                    k+=2;
                }else{
                    f = false;
                    break;
                }
            }
        }
    }
    return f;
}
int main() {
    // for(int i=0;i<)
    
    if(n<m){
        swap(n,m);
    }
    while (true) {
        cin >> n >> m;
        if (n == 0 && m == 0) {
            return 0;
        }
        if ((n * m) & 1) {
            cout << 0 << endl;
            continue;
        }
        memset(dp, 0, sizeof dp);
        for(int i=0;i<(1<<m);i++){
            bool f = true;
            for(int j=0;j<m;){
                if((i>>j)&1){
                    if(!((i>>(j+1))&1 && j+1<m)){
                        f = false;
                        break;
                    }else{
                        j+=2;
                    }
                }else{
                    j++;
                }
            }
            dp[0][i] = f;
        }
        for(int i=1;i<n;i++){
            for(int j=0;j<(1<<m);j++){
                if(dp[0][j]){
                    for(int k=0;k<(1<<m);k++){
                        if(ck(j, k)){
                            dp[1][k]+=dp[0][j];
                        }
                    }
                }
            }
            for(int j=0;j<(1<<m);j++){
                dp[0][j] = dp[1][j];
                dp[1][j] = 0;
            }
        }
        cout<<dp[0][(1<<m)-1]<<endl;
    }
}

然后是使用轮廓线dp,时间复杂度相比常规状压dp更优

使用图1的状态表示方式源程序

#include<iostream>
#include<cstring>
using namespace std;
const int ms = 1 << 12;
long long dp[2][ms];
bool legal[ms];
int n, m;
int main() {
    // for(int i=0;i<)

    if (n < m) {
        swap(n, m);
    }
    while (true) {
        cin >> n >> m;
        if (n == 0 && m == 0) {
            return 0;
        }
        if ((n * m) & 1) {
            cout << 0 << endl;
            continue;
        }
        memset(dp, 0, sizeof dp);
        int now = 0;
        dp[0][(1<<m)-1] = 1;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                now ^= 1;
                memset(dp[now], 0, sizeof dp[now]);
                for (int s = 0; s < (1 << m); s++) {
                    if (dp[now ^ 1][s]) {
                        if(!((s>>(m-1))&1)) {//上放
                            dp[now][((s<<1)&((1<<m)-1)) | 1] += dp[now^1][s];
                        }
                        else if (!(s & 1) && j!=0) {//左放
                            dp[now][(s << 1) & ((1 << m) - 1)] += dp[now ^ 1][s];
                            dp[now][((s << 1) & ((1 << m) - 1)) | 3] += dp[now^1][s];
                        }
                        else {
                            dp[now][(s << 1) & ((1 << m) - 1)] += dp[now ^ 1][s];
                        }
                    }
                }
            }
        }
        cout << dp[now][(1<<m)-1] << endl;
    }
}

使用图3表示状态的方式源程序

#include<iostream>
#include<cstring>
using namespace std;
const int ms = 1 << 12;
long long dp[2][ms];
bool legal[ms];
int n, m;
int main() {
    // for(int i=0;i<)

    if (n < m) {
        swap(n, m);
    }
    while (true) {
        cin >> n >> m;
        if (n == 0 && m == 0) {
            return 0;
        }
        if ((n * m) & 1) {
            cout << 0 << endl;
            continue;
        }
        memset(dp, 0, sizeof dp);
        int now = 0;
        dp[0][(1<<m)-1] = 1;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                now ^= 1;
                memset(dp[now], 0, sizeof dp[now]);
                for (int s = 0; s < (1 << m); s++) {
                    if (dp[now ^ 1][s]) {
                        int left = j>0?(s>>(j-1))&1:0;
                        int up = (s>>j)&1;
                        if (!up) { //不需要判断 i != 0, 因为设置了第-1行的路廓线全为1的方案数为1,其余为0
                            dp[now][s^(1<<j)] += dp[now^1][s];
                        }
                        else if (!left && j != 0) {
                            dp[now][s^(1<<(j-1))] += dp[now^1][s];
                            dp[now][s^(1<<j)] += dp[now^1][s];
                        }
                        else {
                            dp[now][s^(1<<j)] += dp[now^1][s];
                        }
                    }
                    // 上述代码可以简化如下
                    //if (dp[now ^ 1][s]) {
                    //    if (j && (s & (1 << j)) && !(s & (1 << j - 1))) {
                    //        dp[now][s ^ (1 << (j - 1))] += dp[now ^ 1][s];
                    //    }
                    //    dp[now][s ^ (1 << j)] += dp[now ^ 1][s]; 
                    //}
                }
            }
        }
        cout << dp[now][(1<<m) - 1] << endl;
    }
}
  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qq_43983809

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值