【算法基础:动态规划】5.4 状态压缩DP

例题列表

291. 蒙德里安的梦想⭐⭐⭐⭐⭐

https://www.acwing.com/problem/content/293/

在这里插入图片描述

当横向方格摆放完成后,纵向方格的拜访方式就已经确定了。(因为我们只要求横向方格的摆放方案。)
在这里插入图片描述
定义 DP 数组
dp[i][j] 表示 第 i 列中,有 j 集合的行伸出来了,即 i - 1 列的这一行放了横着的小方格。

例如这里有 5 行,j 的范围就是 0 ~ 1<<5 。

比较相邻的两列 j 和 k:
在这里插入图片描述
只要满足这两个条件就可以转移过来:

  1. (j & k) == 0 表示:没有冲突
  2. j | k 不存在连续的奇数个 0 表示:中间的间隔可以放下纵向的格子

注意这道题不能预先处理出所有的 st 数组,因为在 m 和 n 不一样时, st 数组中的值也会变得不一样。

dp 数组初始化:dp[0][0] = 1;

import java.io.BufferedInputStream;
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sin = new Scanner(new BufferedInputStream(System.in));
        while (true) {
            int n = sin.nextInt(), m = sin.nextInt();
            if (n == 0 && m == 0) return;
            op(n, m);
        }
    }

    static void op(int n, int m) {
        boolean[] st = new boolean[1 << n];
        for (int i = 0; i < 1 << n; ++i) {
            int cnt = 0;
            st[i] = true;
            for (int j = 0; j < n; ++j) {
                if ((i >> j & 1) == 1) {
                    if ((cnt & 1) == 1) st[i] = false;
                    cnt = 0;
                } else cnt++;
            }
            if ((cnt & 1) == 1) st[i] = false;
        }

        long[][] dp = new long[m + 1][1 << n];
        dp[0][0] = 1;
        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]) {
                        dp[i][j] += dp[i - 1][k];
                    }
                }
            }
        }
        System.out.println(dp[m][0]);
    }
}

更多参考资料可见:
431 状态压缩DP 蒙德里安的梦想【动态规划】
Acwing291. 蒙德里安的梦想:状态压缩dp

91. 最短Hamilton路径⭐⭐⭐

https://www.acwing.com/activity/content/problem/content/1011/

在这里插入图片描述

先枚举集合,再枚举到达该集合的最后一个点,再枚举可以转移到该点该集合的状态。

dp 过程一共三重循环。

import java.io.BufferedInputStream;
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sin = new Scanner(new BufferedInputStream(System.in));
        int n = sin.nextInt();
        int[][] d = new int[n][n];
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < n; ++j) {
                d[i][j] = sin.nextInt();
            }
        }

        // dp[i][j] 表示达到位置i,已经到达过的位置集合为j时的最短路径
        long[][] dp = new long[n][1<<n];
        for (int i = 0; i < n; ++i) Arrays.fill(dp[i], Long.MAX_VALUE / 2);
        dp[0][1] = 0;
        // 枚举每个可以到达的状态集合
        for (int j = 0; j < 1 << n; ++j) {
            // 枚举该集合可以到达的所有点
            for (int i = 0; i < n; ++i) {
                if ((j >> i & 1) == 1) {            // 如果位置i是1
                    for (int k = 0; k < n; ++k) {   // 从其它k=1但i位置=0的地方转移过来
                        if ((j >> k & 1) == 1) {
                            dp[i][j] = Math.min(dp[i][j], dp[k][j ^ (1 << i)] + d[k][i]);
                        }
                    }
                }
            }
        }
        System.out.println(dp[n - 1][(1 << n) - 1]);
    }
}

注意:为什么要先枚举每个可以到达的状态集合 j,然后枚举该集合中可以到达的所有点 i,而不可以将枚举顺序反过来?
因为:
在这里插入图片描述

也就是先枚举点的话 不能保证前面的状态都被计算过。

相关链接

从集合论到位运算——常见位运算技巧及相关习题 & 状态压缩DP
【力扣周赛】第 355 场周赛(构造&二分答案&异或前缀 状态压缩⭐)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Wei *

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

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

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

打赏作者

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

抵扣说明:

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

余额充值