整理:状压DP

关于状压DP的整理

1.什么是状压DP

状压DP,就是将DP状态压缩为一个数,用这个数的二进制来表示状态,这样既避免了超多维的状态,也避免了状态中不能出现数组的窘境。

一般来说,我们可以认为状态压缩的本质就是将多维互不干扰并且只有有/没有,去过/没去过,取过/没取过这一类非黑即白的状态压缩成一个状态,就比说 \(dp_{i,flag_1,flag_2,...,flag_n}\)\(dp_{i,x}\) 的实质是一样的,只要我们用 \(x\) 在二进制下的每一位分别对应 \(flag_i\) 即可。

而这种指数级的时空复杂度导致了数据范围必定不大的限制,也就说对于一般的状压DP题,\(n\) 几乎都不会大于 \(20\),而对于状态压缩成 \(3\) 进制的题,\(n\) 最大基本上只有 \(15\) 左右。

并且,状压DP的题基本不是什么难题(除了部分黑题),只要看出是状压DP,那么题目就基本结束了。

2.状压DP的几个技巧

1.枚举子集

可以极大的优化时间复杂度,从 \(O(4^n)\) 变成 \(O(3^n)\),可以说是一个极大的优化了。

基本思路为,对于 \(x\),我们不需要枚举所有 \(y,(y<2^n)\) 的数去判断是否是 \(x\) 的子集,而是直接枚举 \(x\) 的子集。

for(int i=C;;i=(i-1)&C){
    //do something……
    if(!i)break;
}

这样我们就可以遍历 \(C\) 所有的子集。

而转移就可以变成

for(int i=1;i<(1<<n);i++){
	for(int j=i;;j=(j-1)&i){
		//do something……
        if(!j)break;
    }
}

这样的总时间复杂度是 \(O(3^n)\),这样做的正确性和时间复杂度证明见 二进制集合操作 - OI Wiki (oi-wiki.org),因为我不会证。

2.lowbit 枚举

与枚举子集的思想一致,我们对于 \(x\),我们不需要尝试所有 \(i\) 看第 \(i\) 位是不是 \(1\)

for(int i=lowbit(C),tmp=C;tmp;tmp-=i,i=lowbit(tmp)){
	//do something……
}

3.预处理状态

状压 \(DP\) 中常常出现一些状态不合法的情况,并且经常会重复判断,我们可以直接预处理出所有可行状态进行转移,最经典的

就是 NOI2001 炮兵阵地,即便是 \(n=10\) 的情况下合法状态也十分少。

甚至可以预处理合法的转移对,也就是什么状态可以转移到什么状态,可以优化一点点。

posted @ 2025-04-06 18:48  陈牧九  阅读(52)  评论(0)    收藏  举报