0 引子
在计算机里,整数是以二进制的方式存储的。把状态信息压缩成二进制当成状态进行动态规划,就是状压DP的基本思想。
是不是一脸懵比?别急着关掉文章,接着往下看,你会发现一个新世界。
0.5 状压能解决什么样的问题?
让我们康康这道题:传送门
很容易想到搜索,不是吗?
然而,我们要用更装比 复杂 优美的方式完成这道题。
1.什么是“状态压缩?”
举个例子吧。例如,例题里的“取哪些砝码”这个信息,就可以压缩进一个int整数里。因为,int整数是二进制存储的。大概长这样:
\[\]
| 十进制 | 二进制(也就是计算机里实际的状态) |
|---|---|
| 1 | 1 |
| 2 | 10 |
| 114514 | 11011111101010010 |
| 6 | 110 |
我们可以发现,数字是由许多二进制位构成的,要么是0要么是1。
是不是发现了什么?
没错,我们可以用每一位的0和1表示每一个砝码选或者不选!
例如,数字 2 (二进制:10) 就可以表示1号砝码不选,2号砝码选的状态。(因为第1位是0,第2位是1)。
知道了如何压缩,就可以用动态规划的方式求解了。
2.如何枚举出每一个状态?
事实上十分简单,这样就可以:
for(int i=0;i<=(1<<n)-1;i++){
}
(1<<n是把1左移n位的意思。例如,1<<3的二进制就是1000,十进制就是8)
我们来模拟一下\(n=3\)时的情况。
| i | i的二进制 |
|---|---|
| 0 | 000 |
| 1 | 001 |
| 2 | 010 |
| 3 | 011 |
| 4 | 100 |
| 5 | 101 |
| 6 | 110 |
| 7 | 111 |
可以发现,i把所有砝码选取情况都枚举出来了。
3.如何转移?
这个其实因题而异,我就用这道题举例子吧
for(int i=1;i<(1<<n);i++){//枚举。因为i=0就是一个也不选,所以不需要枚举。
int high_bit=0,high_bit_num=0;
for(int j=31;j>=0;j--){//int最多有31位,所以j=31-0。
if((i>>j)&1){//意思是i的第j位是否为1
high_bit=1<<j;
high_bit_num=j;
break;//找到了就退出来
}
}//求出当前方案的最高位
sum[i]=sum[i^high_bit]+a[high_bit_num+1];//转移。因为i是“按顺序”枚举的,所以去掉最高位后的方案一定枚举过了。
//i^high_bit的意思是去掉最高位
cnt[i]=cnt[i^high_bit]+1;
}
(这段代码求的是每种砝码选取方案的重量和与每种砝码选取方案要用多少砝码)
注释写的很详细,我就不再罗嗦了。
4.如果你想A掉这道题
你可以康康我的题解:传送门
但是我还是建议你自己做出来
5.写在最后
状压dp和其它所有算法一样,需要大量的练习才能掌握。
这篇文章写的可能不怎么全面,欢迎在评论区提问或指出。另外,不要吊死在一棵树上,多康康别人的文章吧。