整理:状压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\) 的情况下合法状态也十分少。
甚至可以预处理合法的转移对,也就是什么状态可以转移到什么状态,可以优化一点点。

浙公网安备 33010602011771号