引入
在做到某些搜索或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哦~
類型題推薦:
如有問題和錯誤,請各位盡情提出,感激不盡~