淺談二進制狀態壓縮【附例題】


引入

在做到某些搜索或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]互不侵犯

題意簡明易懂,就不多做解釋了。
那么這道題怎么做呢?
可以明確的一點是:這是一道狀壓dp
因為這篇文章主要講的是狀態壓縮,所以dp方程就不具體推了
我們建立dp[i][j][k]來存儲第i行第j個狀態,且放了k個國王的方案數
 
第一步,列出所有可能成立的單行狀態
因為國王可以攻擊到左右兩格,所以可以通過上面的(a&(a<<1))||(a&(a>>1))來排除一些不可能成立的狀態
我們將所有成立的狀態存儲到一個數組里便於取用
注意全0狀態也算進去
 
第二步,初始化
先列出第一行每一種狀態的方案數,以便於后面的dp使用
 
第三部:動規
枚舉i,j,l,k,這里l指第i-1行的狀態是第l種,j指第i行的狀態是第j種,並且排除掉上下行的國王可以互相攻擊到的情況
用a代表j狀態,b代表l狀態
排除方法:if((a&b)||(a&(b<<1))||(a&(b>>1)))continue;
接下來再通過dp[i][j][k]+=dp[i-1][l][k-sum(a)]的dp方程求出最終要求的方案數,這里sum(a)指a在二進制下1的個數
 
code:
#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哦~

 

類型題推薦:

[USACO06NOV]玉米田Corn Fields

[NOI2001]炮兵陣地

 

如有問題和錯誤,請各位盡情提出,感激不盡~

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM