状压DP

你说得对,但是状压DP是一个强力的工具,它可以把一堆状态压缩成一两个压缩状态,从而优化代码、减少码量。


状压DP

状压DP是什么

状压DP,顾名思义,即把状态给压缩的DP。状压DP通常利用二进制,从而把一大堆是或否的维度缩减成了一个数
例如:
您的DP需要建立10个维度,分别为选不选 \(a_1\) ,选不选 \(a_2\) ,选不选 \(a_3\) ,选不选……
如果您不会状压,那么恭喜您,可能要用到这个:dp[3][3][3][3][3][3][3][3][3][3]
而且一旦维度数量变化,那就没法玩了,迎接 \(O(n!)\) 的全排列吧
但只要会了状压,那么恭喜你,可能只需这个:dp[1030]
维度变化也不怕,后面会讲。

状压DP怎么用

仍以上面这个为例。
dp[472]代表的是什么呢?我们先把它转为二进制

\[(472)_{10} = (1 \space 1 \space 1 \space 0 \space 1 \space 1 \space 0 \space 0 \space 0)_{2} \]

从右往左数, \(a_0\) 对第一位, \(a_1\) 对第二位,以此类推。
于是,dp[472]就代表着选择了 \(a_4\) \(a_5\) \(a_7\) \(a_8\) ,从而表示状态。
我们就可以结合& | ~ ^等等位运算快速判断
值得一提的是,二进制可以补充前导0,相当于不选,因此可以优秀的扩展,只需要满足最大维度即可。

例题

洛谷P1896互不侵犯
题意:在一个 \(N\times N\) 的棋盘上,放 \(K\) 个国王,是之互不吃,求方案数。
裸的状压。用二进制储存当前的情况,于是就可以有 dp[i][j][s] ,其中 \(i\) 为行数, \(j\) 为已放几个王, \(s\)表示这一行的情况。
其他东西自己写咯

点击查看代码
#include<bits/stdc++.h>
using namespace std;
long long n,k,dp[15][85][520],sum[520],ans;
bool check(int a,int b){
	if((a&(a<<1))!=0 || (b&(b<<1))!=0)return 0;
	if((a&b)!=0)return 0;
	if(((a<<1)&b)!=0)return 0;
	if(((b<<1)&a)!=0)return 0;
	return 1; 
}int x,y;
int main(){
	cin>>n>>k;
	sum[0] = 0;
	sum[1] = 1;
	for(int i=2;i<(1<<n);i++){
		if(i%2==0)sum[i] = sum[i>>1];
		else sum[i] = sum[i>>1]+1; 
	}
	dp[0][0][0] = 1;
	for(int i=1;i<=n;i++){
		for(int s=0;s<(1<<n);s++){
			for(int j=0;j<(1<<n);j++){
				if(check(j,s)){
					for(int w=sum[s];w<=k;w++)
					dp[i][w][s] += dp[i-1][w-sum[s]][j];
				}
			}
		}
	}
	for(int i=0;i<(1<<n);i++){
		ans+=dp[n][k][i];
	}cout<<ans;
	return 0;
}


你说得对,但是状压DP有着指数级的复杂度, \(n\) 不能大于25。

posted @ 2025-07-18 18:09  ___jungle  阅读(21)  评论(0)    收藏  举报