動態規划之狀態壓縮


狀態壓縮動態規划(簡稱狀壓dp)是另一類非常典型的動態規划,通常使用在NP問題的小規模求解中,雖然是指數級別的復雜度,但速度比搜索快,其思想非常值得借鑒。

為了更好的理解狀壓dp,首先介紹位運算相關的知識。

1.’&’符號,x&y,會將兩個十進制數在二進制下進行與運算,然后返回其十進制下的值。例如3(11)&2(10)=2(10)。

2.’|’符號,x|y,會將兩個十進制數在二進制下進行或運算,然后返回其十進制下的值。例如3(11)|2(10)=3(11)。

3.’^’符號,x^y,會將兩個十進制數在二進制下進行異或運算,然后返回其十進制下的值。例如3(11)^2(10)=1(01)。

4.’<<’符號,左移操作,x<<2,將x在二進制下的每一位向左移動兩位,最右邊用0填充,x<<2相當於讓x乘以4。相應的,’>>’是右移操作,x>>1相當於給x/2,去掉x二進制下的最有一位。

這四種運算在狀壓dp中有着廣泛的應用,常見的應用如下:

1.判斷一個數字x二進制下第i位是不是等於1。

方法:if ( ( ( 1 << ( i - 1 ) ) & x ) > 0)

將1左移i-1位,相當於制造了一個只有第i位上是1,其他位上都是0的二進制數。然后與x做與運算,如果結果>0,說明x第i位上是1,反之則是0。

2.將一個數字x二進制下第i位更改成1。

方法:x = x | ( 1<<(i-1) )

證明方法與1類似,此處不再重復證明。

3.把一個數字二進制下最靠右的第一個1去掉。

方法:x=x&(x-1)

感興趣的讀者可以自行證明。

下面來一道狀態壓縮的例題(然而弱雞的我例題都做了好久。。。)

https://www.luogu.org/problemnew/show/1896

整理一下思路。

所謂的狀態壓縮,我個人認為是用時間換方便,將所有情況匯集在一個數里,本來是要用多個維度才能表示的情況換作一個維度。

可能一道題有多種狀態,而每種狀態都需要我們去討論,而如果為每個狀態都單獨開一個數組來存儲狀態,那就GG了,根本存不下。

所以我們就把10進制的轉換為二進制來存儲,二進制每一位0,1都可以表示選擇與否,一個二進制數就可以完成對多種狀態的存儲。

然而對與時間復雜度卻是指數級的,所以對於數據范圍大的,這個就無法使用了。

來波代碼

 1 #include<cstdio>
 2 #include<cstring>
 3 using namespace std;
 4 long long f[100][900][1000];//f[行數][國王個數][狀態];
 5 int way[105];
 6 int num[105];
 7 int find(int n,int all)
 8 {
 9     int j=0;
10     for(int i=0;i<=all;i++)
11     {
12         if((i&(i<<1))==0&&(i&(i>>1))==0)
13         {            
14             j++;
15             way[j]=i;
16             int x=i;
17             int nn=0;
18             while(x>0)
19             {
20                 nn++;
21                 x=x-(x&(-x));
22                 //每執行一次這項操作,會消去x作為二進制數時最右端的一個1。 
23             }
24             num[j]=nn;
25         }
26     }
27     return j;
28 }
29 bool search(int s2,int s1)//判斷兩種狀態是否沖突。
30 {
31     if((s2&(s1<<1))==0&&(s1&(s2<<1))==0&&(s1&s2)==0)
32     return true;
33     else
34     return false;
35 }
36 int main()
37 {
38     int n,k1;
39     scanf("%d%d",&n,&k1);
40     int all=1<<n;
41     all--;//總共存在的狀態數
42     int nway=find(n,all);//找出一行中不會沖突的最多方案的個數
43     for(int i=1;i<=nway;i++)
44     {
45         f[1][num[i]][way[i]]=1;
46     }
47     for(int i=2;i<=n;i++)//行數
48     for(int j=1;j<=nway;j++)//上一行狀態 
49     for(int t=1;t<=nway;t++)//這一行狀態 
50     {
51         if(search(way[j],way[t]))
52         {
53             for(int g=num[t];g<=k1;g++)
54             {
55                 f[i][g][way[t]]+=f[i-1][g-num[t]][way[j]];
56                 //狀態轉移方程。
57             }
58         }
59     }
60     long long tot=0ll;//記住要使用long long型不然就像我一樣交了半天都不對。
61     for(int i=1;i<=nway;i++)
62     {
63         tot+=f[n][k1][way[i]];
64     }
65     printf("%lld",tot);
66     return 0;
67 } 

 


免責聲明!

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



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