引入
在做到某些搜索或dp题时,我们常常会发现如果用一个数组去存储一些仅有1和0的状态,在进行存储和更改时时间和空间都很可能会爆掉。这时候我们就要借助二进制进行状态压缩,使得一个01数组变成一个二进制数(以十进制存储),从而达到缩小时空复杂度的目的。
Ps:关于二进制状态压缩有一个误区:初学者常会认为我们需要将存储的十进制状态转化为二进制以进行修改操作,从而误认为状态压缩仅能缩小时间复杂度。其实,位运算能很好地解决这个问题。关于位运算修改状态的方法,在下文中会提及。
如何进行状压?
对于一个01数组a[],我们用一个二进制数替代它。例如,a[]={0,1,1,0,0},则进行状态压缩后的a=01100=12,这样一来空间就缩小了许多。当然,二进制状压所能压缩的远不止5位二进制数。但是在进行压缩时,我们也要注意压缩后的数字不能超过int(long long)的范围。反过来说,在看到一些题目中n<=31或m<=31时,我们可以很自然的想到二进制状态压缩。
一些基本的状态查询修改操作:
在进行状态查询和修改时,我们会用到位运算符号,如&(按位与),|(按位或),^(异或),<<,>>等。
这里要注意两点:
1.千万不要将&、|这些按位运算符号打成&&、||
2.由于位运算符号的优先级一般都很低,所以我们要养成随时加括号的习惯
接下来是一些基本的操作,在下文中a指当前状态,n为位数(证明很简单,略):
查询当前状态第i位是否为1:a&(1<<i)
将当前状态第i位取反(1->0或0->1):a^=(1<<i)
判断当前状态在二进制下是否有连续的若干个1:(a&(a<<1))||(a&(a>>1))
当前状态二进制下取反:a^=((1<<n)-1)
例题:
[SCOI2005]互不侵犯
#include <cstdio> #include <iostream> using namespace std; int n,k,a,b,tot,use[1001]; long long dp[11][1001][101]; long long ans; int sum(int s){ int num=0; while(s){ if(s%2)num++; s/=2; } return num; } int main(){ scanf("%d%d",&n,&k); for(int i=0;i<=(1<<n)-1;i++) if(!(i&(i>>1)))use[++tot]=i; for(int i=1;i<=tot;i++){ dp[1][i][sum(use[i])]=1; } for(int i=2;i<=n;i++){ for(int j=1;j<=tot;j++){ for(int t=1;t<=tot;t++){ a=use[j],b=use[t]; if((a&b)||(a&(b<<1))||(a&(b>>1)))continue; for(int p=sum(a);p<=k;p++){ dp[i][j][p]+=dp[i-1][t][p-sum(a)]; } } } } for(int i=1;i<=tot;i++){ ans+=dp[n][i][k]; } printf("%lld\n",ans); return 0; }
最后别忘了long long哦~
类型题推荐:
如有问题和错误,请各位尽情提出,感激不尽~