状态压缩 DP

状态压缩 DP

概念

状态压缩DP(又称集合动态规划,以下简称状压DP),是一种以集合状态为状态解决问题的动态规划算法。

什么是以集合为状态

举个常用的例子:集合内每个元素的使用情况。这类似搜索中的标记数组(使用过被标记,防止重复使用)。但搜索中的标记数组是一个数组,而在状压DP中使用很多个数组储存所有使用情况非常消耗空间。这时便有了状态压缩

基本思想

状态压缩

依然使用使用集合内每个元素的使用情况为例:

观察标记数组 \(book =\{0,1,0,1,0,0,0,1\}\),其中 \(1\) 代表已使用\(0\) 代表未使用

将所有 \(0\)\(1\) 按顺序紧密排列在一起就会得到一个 \(01\) 串:\(01010001\),容易发现这代表一个十进制数 \(81\)二进制形式。这样我们就成功把一个标记数组转换为了一个数字

位操作

如果想要像操作数组一样操作这个二进制数,我们就会运用到位操作:

  1. 右移x >> y,本质上是删除一个二进制数 \(x\)右端 \(y\) 个数字,整体向右移动( 即\(\cfrac{x}{2^y}\)),如:

    12 >> 2 \(\Rightarrow\) 二进制(1100) >> 2 \(=\) 二进制(0011) \(\Rightarrow\) 3

    \(\cfrac{12}{2^2} = \cfrac{12}{4} = 3\)

  2. 左移x << y,与右移相反本质上是在一个二进制数 \(x\)右端加上 \(y\)\(0\),整体向左移动(即 \(x\times 2^y\)),如:

    3 << 2 \(\Rightarrow\) 二进制(0011) << 2 \(=\) 二进制(1100) \(\Rightarrow\) 3

    \(3 \times 2^2 = 3 \times 4 = 12\)

  3. 按位与x & y,将 \(x\)\(y\) 的每一位比较。如果都为 \(1\),答案的这一位也为 \(1\),反之则为 \(0\) ,如:

    3 & 6 \(\Rightarrow\) 二进制(0011) & 二进制(0110)

    x = \(3\) 0 0 1 1
    y = \(6\) 0 1 1 0
    ans 0 0 1 0

    \(ans = (0010)_2 = 2\)

    3 & 6 = 2

  4. 按位或x | y,同理,将 \(x\)\(y\) 的每一位比较。如果至少有一个为 \(1\),答案的这一位也为 \(1\),反之则为 \(0\)

  5. 按位非~x,同理,将 \(x\) 的每一位取到相反的数。如果为 \(1\),则答案的这一位为 \(0\),反之则为 \(1\)

对于当前状态 \(s\) ,我们会有以下操作:

  1. 查询第 \(i\) 位的值,如果 (s & (1 << i)) 为真(答案不为 \(0\))则第 \(i\) 位为 \(1\) ,反之则为 \(0\)
  2. 将第 \(i\) 位设为 \(1\)s | (1 << i) ,只将 \(i\) 位设为 \(1\) ,再按位或
  3. 将第 \(i\) 位设为 \(0\)s & ~(1 << i) ,除第 \(i\) 位外,其余为均为 \(1\) , 按位与后不变

算法实现

我们先来看一道简单的题目,洛谷P1896 [SCOI2005] 互不侵犯

\(N \times N\) 的棋盘里面放 \(K\) 个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上,下,左,右,左上,左下,右上,右下八个方向上附近的各一个格子,共 \(8\) 个格子。(\(1\leq N\leq 9\)

看到这个问题很容易联想到经典的八皇后问题,但这是国王(攻击范围很小),所以方案也变得很多,搜索无法完成:

我们用状压DP,设定DP状态 \(dp[i,s,k]\) 表示第 \(i\) 行状态为 \(s\) 已放置 \(k\) 个国王的方案总数,我们可以得到它的状态转移方程:

\[dp_{i,s,k}=\sum_{s与s_0不冲突}dp_{i-1,s_{0},k_{0}} \]

这里 \(s_0\) 表示上一行的状态,\(k_0\) 表示截止上一行共选了几个

\(N = 9\) 时:时间复杂度近似为 \(O(9\times 9^2 \times 81 \times 81)\),即 \(O(6 \times 10^6)\)

所以状压DP的数据范围的某一维通常会很小

代码实现:

  1. 初始化所有可能状态

    int bookcnt = 0; // 可能状态
    int book[(1 << maxn) + 5],num[(1 << maxn) + 5]; // 可能状态以及这个状态有几个国王(几个1)
    void init() {
    	for (int i = 0;i < (1 << n);i ++) { // 枚举状态
    		if (i & (i << 1)) continue; // 与前一位冲突(左右能攻击到)
    		bookcnt ++; // 新方案
        book[bookcnt] = i; // 具体方案十进制数
    		for (int j = 0;j < maxn;j ++) if (i & (1 << j)) num[bookcnt] ++; // 含1量
    	}
    }
    
  2. 动态规划主体

    for (int i = 1;i <= n;i ++) // 枚举行
    		for (int si = 1;si <= bookcnt;si ++) // 这一行的状态
    			for (int sn = 0;sn <= k;sn ++) // 枚举剩余国王数量
    				if (sn >= num[si]) // 剩余国王数量足够
    					for (int sj = 1;sj <= bookcnt;sj ++) // 上一行的状态
    						if (
                  !(book[si] & book[sj]) && // 与上方无冲突
                  !(book[sj] & (book[si] << 1)) && // 与左斜方无冲突
                  !(book[sj] & (book[si] >> 1)) // 与右斜方无冲突
                ) dp[i][si][sn] += dp[i - 1][sj][sn - num[si]]; // 方案数增加
    
  3. 统计答案

    ll ans = 0;
    for (int i = 1;i <= bookcnt;i ++) ans += dp[n][i][k]; // 最后一行的所有情况
    printf("%lld",ans);
    

完整代码

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 9;
int n,k;
ll dp[maxn + 5][(1 << maxn) + 5][maxn * maxn + 5];
int bookcnt = 0;
int book[(1 << maxn) + 5],num[(1 << maxn) + 5];
void init() {
	for (int i = 0;i < (1 << n);i ++) {
		if (i & (i << 1)) continue;
		int cnt = 0;
		for (int j = 0;j < maxn;j ++) if (i & (1 << j)) cnt ++;
		bookcnt ++;
		book[bookcnt] = i;
		num[bookcnt] = cnt;
	}
}
int main() {
	scanf("%d %d",&n,&k);
	init();
	dp[0][1][0] = 1ll;
	for (int i = 1;i <= n;i ++)
		for (int si = 1;si <= bookcnt;si ++)
			for (int sn = 0;sn <= k;sn ++)
				if (sn >= num[si])
					for (int sj = 1;sj <= bookcnt;sj ++)
						if (!(book[si] & book[sj]) && !(book[sj] & (book[si] << 1)) && !(book[sj] & (book[si] >> 1)))
							dp[i][si][sn] += dp[i - 1][sj][sn - num[si]];
	ll ans = 0;
	for (int i = 1;i <= bookcnt;i ++) ans += dp[n][i][k];
	printf("%lld",ans);
	return 0;
}

扩展

三进制状态压缩

三进制状态压缩与二进制状态压缩类似:

  1. 获取 \(x\) 的第 \(i\) 位,利用进制的特性,这一位的值为 \(\cfrac{x}{3^k}~mod~3\)
  2. 更新 \(x\) 的第 \(i\) 位为 \(v\)\(x + (v-(\cfrac{x}{3^k}~mod~3))·3^k\)

除此之外,其余思路几乎相同

例题

洛谷P2704 [NOI2001] 炮兵阵地:与前两排都有关系

posted @ 2025-04-25 21:06  nightmare_lhh  阅读(41)  评论(0)    收藏  举报