以下全篇下标都是从 1 1 1 开始,习惯从 0 0 0 开始的自行代换(。
前置技能
1.枚举 S S S 的子集:
for(int st=S;st;st=(st-1)&S)//st为S的非空子集
对于空集需要额外的判断。
如果只枚举非空真子集,写成:
for(int st=S&(S-1);st;st=(st-1)&S)//st为S的非空真子集
注意状态是从大到小枚举的。如果要从小到大枚举状态,把上面的每个枚举的状态压进栈里。
2.枚举包含 S S S 的集合:
C=S^U;for(int st=C;st;st=(st-1)&C)//(st|S)为包含S的集合
其中 U U U 是全集,通常为 ( 1 < < n ) − 1 (1<<n)-1 (1<<n)−1
注意上面枚举的集合是不包括 S S S 的。
3.枚举 S S S 中的 1 1 1 :
因为能够状态压缩的 n n n 都很小,一般来说就对于每一位枚举的话也不会超时:
for(int i=1;i<=n;i++)if(S&(1<<i-1))//状态S中第i位为1
or
for(int i=1;i<=n;i++)if(S>>(i-1)&1)//状态S中第i位为1
更快一点的:
for(int i=0;i<n;i++)log[1<<i]=i;
for(int st,i;S;S^=st)
{
st=S&(-S);i=log[st]+1;
//状态S中第i位为1
}
其实就是利用了 l o w b i t lowbit lowbit 的思想,脑补一下应该都会。
4.其他进制:
对于二进制中,右移 x x x 表示除以 2 x 2^x 2x,左移 x x x 表示乘以 2 x 2^x 2x
类推的话,对于 p p p 进制:
bit[0]=1;for(int i=1;i<=n;i++)bit[i]=bit[i-1]*p;
//枚举状态S的每一位:
for(int i=1;i<=n;i++)
{
int j=S/bit[i-1]%p;//j表示S状态中第i位上的数字
}
枚举 p p p 进制下的子集一般用不到(其实我也不会orz)
如果空间大小允许建议把题目当成 2 x 2^x 2x 进制来做(例如把 3 3 3 进制当 4 4 4 进制),因为可以用位运算来提高时间效率。
5.判断 s t st st 是否是 S S S 的子集:
if((s&st)==st)//为1,st即为S子集
注意位运算优先级。
例题
待填。