夯基礎之手撕javascript繼承詳解
二進位制的很多應用離不開集合這個概念,我們都知道在計算機當中,所有資料都是以二進位制的形式儲存的。一般一個int整形是4個位元組,也就是32位bit,我們通過這32位bit上0和1的組合可以表示多大21億個不同的數。如果我們把這32位bit看成是一個集合,那麼每一個數都應該對應集合的一種狀態,並且每個數的狀態都是不同的。
比如上圖當中,我們列舉了5個二進位制位,我們把其中兩個設定成了1,其餘的設定成了0。我們通過計算,可以得到6這個數字,那麼6也就代表了(00110)這個狀態。數字和狀態是一一對應的,因為每個整數轉化成二進位制都是唯一的。
也就是說一個整數可以轉化成二進位制數,它可以代表某個集合的一個狀態,這兩者一一對應。這一點非常重要,是後面一切推導的基礎。
狀態轉移
整數的二進位制表示可以代表一個二元集合的狀態,既然是狀態就可以轉移。在此基礎上,我們可以得出另一個非常重要的結論——我們可以用整數的加減表示狀態之間的轉移。
我們還用剛才的例子來舉例,上面的圖當中我們列舉了5個二進位制位,假設我們用這5個二進位制位表示5個小球,這些小球的編號分別是0到4。這樣一來,剛才的6可以認為表示拿取了1號和2號兩個小球的狀態。
如果這個時候我們又拿取了3號小球,那麼集合的狀態會發生變化,我們用一張圖來表示:
上圖當中粉絲的筆表示決策,比如我們拿取了3號球就是一個決策,在這個決策的影響下,集合的狀態發生了轉移。轉移之後的集合代表的數是14,它是由之前的集合6加上轉移帶來的變化,也就是得到的。剛好就代表拿取3號球這個決策,這樣我們就把整個過程串起來了。
總結一下,我們用二進位制的0和1表示一個二元集合的狀態。可以簡單認為某個物品存在或者不存在的狀態。由於二進位制的0和1可以轉化成一個int整數,也就是說我們用整數代表了一個集合的狀態。這樣一來,我們可以用整數的加減計算來代表集合狀態的變化。
這也就是狀態壓縮的精髓,所謂的壓縮,其實就是將一個集合壓縮成了一個整數的意思,因為整數可以作為陣列的下標,這樣操作會方便我們的編碼。
最短Hamilton路徑問題(也就是大名鼎鼎的旅行商問題)
給定一張n個點的帶權無向圖,點從 0~n-1 標號,求起點 0 到終點 n-1 的最短Hamilton路徑。 Hamilton路徑的定義是從 0 到 n-1 不重不漏地經過每個點恰好一次。
輸入格式
第一行輸入整數nn。
接下來nn行每行nn個整數,其中第ii行第jj個整數表示點ii到jj的距離(記為a[i,j])。
對於任意的x,y,zx,y,z,資料保證 a[x,x]=0,a[x,y]=a[y,x] 並且 a[x,y]+a[y,z]>=a[x,z]。
輸出格式
輸出一個整數,表示最短Hamilton路徑的長度。
資料範圍
1≤n≤201≤n≤20
0≤a[i,j]≤1070≤a[i,j]≤107
輸入樣例:
5
0 2 4 5 1
2 0 6 5 3
4 6 0 8 3
5 5 8 0 5
1 3 3 5 0
輸出樣例:
18
1 #include<iostream> 2 #include<cstring> 3 #include<algorithm> 4 5 using namespace std; 6 7 const int N=20,M=1<<N; 8 long long int dp[M][N]; 9 int w[N][N]; 10 int n; 11 12 int main() 13 { 14 cin>>n; 15 16 for(int i=0;i<n;i++) 17 for(int j=0;j<n;j++) 18 cin>>w[i][j]; 19 20 memset(dp,0x3f,sizeof dp); 21 dp[1][0]=0; 22 for(int i=0;i<(1<<n);i++) 23 for(int j=0;j<n;j++) 24 if(i>>j&1) 25 for(int k=0;k<n;k++) 26 if(i-(1<<j)>>k&1) 27 dp[i][j]=min(dp[i][j],dp[i-(1<<j)][k]+w[k][j]); 28 29 cout<<dp[(1<<n)-1][n-1]; 30 31 return 0; 32 }