状态压缩dp

前言

状态压缩就是用某个方法,以最小代价来表示某种状态,通常是用二进制数来表示各个点的状态。

有些问题,若用常规dp,由于枚举的状态太多,会TLE,就需要使用状态压缩!

题目

①国际象棋

https://www.luogu.com.cn/problem/P8756

状态:1010101010 (第i个1代表当前行第i列放了马)

本题将行与列导致,并不影响结果,但极大的减小了每一行枚举的状态数!

dp[i][j][a][x]

表示前i行放了j只羊,第i - 1行的状态为a,第i行的状态为x的所有方案数

遍历第i - 2行的状态b,状态转移方程为:

dp[0][0][0][0] = 1

dp[i][j][a][x] = dp[i][j][a][x] + dp[i - 1][j - count(x)][b][a]

n,m,k = map(int,input().split())
MOD = 1000000007
dp = [[[[0 for _ in range(1 << n)]for _ in range(1 << n)]for _ in range(k + 1)]for _ in range(m + 1)]
# 初 始 值 设 为 1,为 之 后 的 递 推 打 下 基 础!
dp[0][0][0][0] = 1
# 计 算 x 有 多 少 个 1,也 就 是 这 个 状 态 包 含 了 多 少 只 羊!
def count(x):
    res = 0
    for i in range(n):
        res += (x >> i) & 1
    return res
# dp[i][j][a][x]
# 表 示 前 i 行 放 了 j 匹 马 而 且 第 i - 1 行 的 状 态 为 a,第 i 行 的 状 态 为 x
for i in range(1,m + 1):
    for j in range(k + 1):
        for a in range(1 << n):
            for x in range(1 << n):
                # 判 断 第 i - 1 行 与 第 i 行 的 马 的 状 态 是 否 有 冲 突
                if (x >> 2) & a != 0 or (x << 2) & a != 0:
                    continue
                # 遍 历 第 i - 2 行 的 状 态
                for b in range(1 << n):
                    # 判 断 第 i - 2 行 与 第 i 行 的 马 的 状 态 是 否 有 冲 突
                    if (x >> 1) & b != 0 or (x << 1) & b != 0:
                        continue
                    # 判 断 第 i - 2 行 与 第 i - 1 行 的 马 的 状 态 是 否 有 冲 突
                    if (a >> 2) & b != 0 or (a << 2) & b != 0:
                        continue
                    # 第 i 行 的 状 态 由 第 i - 1 行 的 状 态 推 出
                    if j >= count(x):
                        dp[i][j][a][x] = (dp[i][j][a][x] + dp[i - 1][j - count(x)][b][a]) % MOD
ans = 0 
for a in range(1 << n):
    for x in range(1 << n):
        ans = (ans + dp[m][k][a][x]) % MOD
print(ans)

②回路计数

状态:1010101010 (第i个1代表已经经过了i号楼)

dp[i][j]

表示当前状态为i,且最后到达j号楼的路径数

那么有dp[i + 1 << k][k] += dp[i][j]

前提是状态不包含k号楼,且j号楼与k号楼之间有路径!

import math
dp = [[0] * (22) for i in range(2100000)]
g = [[0 for i in range(22)] for i in range(22)]
n = 1 << 21
for i in range(1, 22):
    for j in range(i, 22):
        if math.gcd(i, j) == 1:
            g[i - 1][j - 1] = g[j - 1][i - 1] = 1
dp[1][0] = 1
i = 1
# 遍 历 所 有 的 状 态 (一 共 有 1 >> 21 个 状 态)!
while i < n:
    # 遍 历 每 个 状 态 最 终 到 达 的 楼!
    for j in range(0, 21):
        # 如 果 i 状 态 不 包 含 j 号 楼,则 跳 过 !
        if (i >> j & 1) == 0:
            continue
        # 遍 历 所 有 i 状 态 , 最 后 到 达 j,并 且 之 后 能 到 达 的 k 状 态 !
        for k in range(0, 21):
            # j 能 到 达 k ,且 i 状 态 不 包 含 k!
            if g[j][k] == 0 or (i >> k & 1) != 0:
                continue
            dp[(i + (1 << k))][k] += dp[i][j]
    i += 1
res = 0
for i in range(0, 21):
    res += dp[n - 1][i]
print(res)

③划分为k个相等的子集

记忆化搜索 递归遍历所有可行状态!

https://leetcode.cn/problems/partition-to-k-equal-sum-subsets/

状态:1010101010 (第i个1代表已经经过选了第i个数)

class Solution:
    def canPartitionKSubsets(self, nums: List[int], k: int) -> bool:
        s = sum(nums)
        if s % k:
            return False
        d = s // k
        # 方 便 下 面 剪 枝
        nums.sort()  
        if nums[-1] > d:
            return False
        n = len(nums)
        # 该题一共有2 ^ n - 1 个 状 态 1 1 1 1 1 1 (n 个 1)
        @cache
        def dfs(s, p):
            # 递 归 出 口,表 示 全 都 已 经 选 了;
            # 如 果 能 到 这 一 步 ,则 说 明 符 合 题 意
            # 否 则,会 因 为 p + nums[i]  break,结 果 为 false
            if s == (1 << n) - 1:
                return True
            for i in range(n):
                # 减 枝
                if nums[i] + p > d:
                    break
                # 如 果 没 有 选 择 了 第 i 个 数!   s >> i & 1 == 0
                # p + nums[i] 等 于 d 时 置 为 0   
                # s ^ (1 << i) 把 s 中 的 第 i 的 0 变 成 1,代表已经选了
                if s >> i & 1 == 0 and dfs(s ^ (1 << i), (p + nums[i]) % d):  
                    return True
            return False 
        return dfs(0, 0)

④优美的排列

https://leetcode.cn/problems/beautiful-arrangement/

状态:1010101010 (第i个1代表已经经过选了第i个数,并且下一个要选的数的下标为i+1)

class Solution:
    def countArrangement(self, n: int) -> int:
        dp = [0] * (1 << n)
        dp[0] = 1
        for mask in range(1,1 << n):
            index = bin(mask).count('1')
            for i in range(n):
                if mask & (1 << i) and (index % (i + 1) == 0 or (i + 1) % index == 0):
                    dp[mask] += dp[mask ^ (1 << i)]
        return dp[(1 << n) - 1]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值