状态压缩dp
状态压缩dp:把每个事件标号,设成二进制下的一位数字,即用单个数字存储一个事件,这就要求每个事件只出现一次
(1)位运算
- 插入
A|=1<<c;
- 删除
A&=~(1<<c);
当确保要删除的位是1时,亦可A^=1<<c;
- lowbit求二进制下最低位的1是多少
A&(-A);
- 清空为空集
A=0;
- 求并集
A|B;
- 求交集
A&B;
- 建立大小为size的全集
ALL=(1<<size)-1;
- 求全集中A的补集
ALL^A;
- 判断B是否是A的子集
(A&B)==B;
- 枚举所有子集情况
1
2
3
4for (int i=0;i<=ALL;++i)
{
//do something
} - 枚举A的所有子集(包括本身和空集)
1
2
3
4
5int subset=A;
{
//do something
subset=(subset-1)&A;
}while(subset!=A) - 求A里面有几个元素(有几个1)
1
2
3
4int count=0;
for (int i=0;i<si;++i)
if ((A&(1<<i))!=0)
++count;1
2
3int count=0;
for (int i=0;i!=0;i>>1)
count+=i&1;1
2
3
4
5int count=0;
while(num!= 0){
num=num&(num-1);
count++;
} - highbit二进制下最高位的1
1
2
3
4
5
6
7int p=lowbit(x);
while (p!=x)
{
x-=p;
p=lowbit(x);
}
return p; - 判断x是否是2的幂次
return (x!=0) && (x&(x-1))==0;
- 判断x是否是偶数
return (x&1)==0;
- 判断x是否是奇数
return (x&1)==1;
- 求全集ALL中每个子集,设cnt[i]表示每个子集里面有几个元素
1
2
3
4//空集自然没有元素
cnt[0]=0;
for (int i=1;i<=ALL;++i)
cnt[i]=cnt[i>>1]+(i&1);
(2)旅行商TSP问题
一张连通图,共有n个节点,从某一点除法,要求所有点至少经过一次,最后回到起始点,边权值为路径,需要满足上述条件并且总路径最小
解: dp[s][i]+w[i][j] 转移到--> dp[s|(1<<j)][j]
s表示当前已经过节点集合,s|(1<<j)表示插入新节点后的转移后的新集合,i表当前集合最后到达的节点
还可以用((1<<j)&s)==0判断新插入的节点是否不是原集合s的子集,来进行剪枝,但本题求的是min亦可不剪
(3)玉米田问题
给定一个二维0-1矩阵,”1”表示不能放东西,”0”表示能放东西,选尽量多的能放东西的格子,并且选的格子之间两两不相邻(选定一个格子a,则格子a的上下左右格子都不能选),求最多能选多少个格子
解:可以按行从上到下枚举,这样就省去考虑下这个方向1
2dp[i][s]=dp[i-1][k]+cnt[s]
且 (s&k)==0 且 (s&g[i]==0) 且 (s&(s>>1))==0
i表示第i行,s表示当前操作行选取的格子的某种情况集合,i-1则表示上一行,k则表示上一行所有可能集合,cnt[s]表示当前s集合中有多少个元素,(s&k)==0保证了第i行的上这个方向不会相邻,g[i]表示第i行原始0-1矩阵的情况集合,这就保证了选择格子时不会选择”1”的格子,(s&(s>>1))==0则保证了同一行左右之间不相邻
当是m*n的矩阵问题时,可对min(m,n)进行状压,max(m,n)作转移方向