論文閱讀 狀態壓縮

狀態壓縮

Abstract

信息學發展勢頭迅猛,信息學奧賽的題目來源遍及各行各業,經常有一些在實際應用中很有價值的問題被引入信息學并得到有效解決。然而有一些問題卻被認為很可能不存在有效的(多項式級的)算法,本文以對幾個例題的剖析,簡述狀態壓縮思想及其應用。

Keywords

狀態壓縮、集合、Hash、NPC

?

?

Content

Introduction

作為OIers,我們不同程度地知道各式各樣的算法。這些算法有的以O(logn)的復雜度運行,如二分查找、歐幾里德GCD算法(連續兩次迭代后的余數至多為原數的一半)、平衡樹,有的以O()運行,例如二級索引、塊狀鏈表,再往上有O(n)、O(nplogqn)……大部分問題的算法都有一個多項式級別的時間復雜度上界,我們一般稱這類問題為P類(deterministic Polynomial-time)問題,例如在有向圖中求最短路徑。然而存在幾類問題,至今仍未被很好地解決,人們懷疑他們根本沒有多項式時間復雜度的算法,它們是NPC(NP-Complete)NPH(NP-Hard)類,例如問一個圖是否存在哈密頓圈(NPC)、問一個圖是否存在哈密頓圈(NPH)、求一個完全圖中最短的哈密頓圈(即經典的Traveling Salesman Problem貨郎擔問題,NPH)、在有向圖中求最長(簡單)路徑(NPH),對這些問題尚不知有多項式時間的算法存在。P和NPC都是NP(Non-deterministic Polynomial-time)的子集,NPC則代表了NP類中最難的一類問題,所有的NP類問題都可以在多項式時間內歸約到NPC問題中去。NPH包含了NPC和其他一些不屬于NP(也更難)的問題(即NPC是NP與NPH的交集), NPC問題的最優化版本一般是NPH的,例如問一個圖是否存在哈密頓圈是NPC的,但求最短的哈密頓圈則是NPH的,原因在于我們可以在多項式時間內驗證一個回路是否真的是哈密頓回路,卻無法在多項式時間內驗證其是否是最短的,NP類要求能在多項式時間內驗證問題的一個解是否真的是一個解,所以最優化TSP問題不是NP的,而是NPH的。存在判定性TSP問題,它要求判定給定的完全圖是否存在權和小于某常數v的哈密頓圈,這個問題的解顯然可以在多項式時間內驗證,因此它是NP的,更精確地說是NPC的。

如上所述,對于NPC和NPH問題,至今尚未找到多項式時間復雜度的算法。然而它們的應用又是如此的廣泛,我們不得不努力尋找好的解決方案。毫無疑問,對于這些問題,使用暴力的搜索是可以得到正確的答案的,但在信息學競賽那有限的時間內,很難寫出速度可以忍受的暴力搜索。例如對于TSP問題,暴力搜索的復雜度是O(n!),如此高的復雜度使得它對于高于10的數據規模就無能為力了。那么,有沒有一種算法,它可以在很短的時間內實現,而其最壞情況下的表現比搜索好呢?答案是肯定的——狀態壓縮(States Compression,SC)

?

作為對下文的準備,這里先為使用Pascal的OIers簡要介紹一下C/C++樣式的位運算(bitwise operation)。

?

  • 基本運算符

名稱

C/C++樣式

Pascal樣式

簡記法則

按位與

(bitwise AND)

&

and

全一則一

否則為零

按位或

(bitwise OR)

|

or

有一則一

否則為零

按位取反

(bitwise NOT)

~

not

是零則一

是一則零

按位異或

(bitwise XOR)

^

xor

不同則一

相同則零

以上各運算符的優先級從高到低依次為:~,&,^,|

?

  • 特殊應用
    1. and:
      1. 用以取出一個數的某些二進制位
      2. 取出一個數的最后一個1(lowbit)?:x&-x
    2. or :用以將一個數的某些位設為1
    3. not:用以間接構造一些數:~0=4294967295=232-1
    4. xor:
      1. 不使用中間變量交換兩個數:a=a^b;b=a^b;a=a^b;
      2. 將一個數的某些位取反

?

?

有了這些基礎,就可以開始了。

?

?

Getting Started

我們暫時避開狀態壓縮的定義,先來看一個小小的例題。

?

【引例】

在n*n(n≤20)的方格棋盤上放置n個車(可以攻擊所在行、列),求使它們不能互相攻擊的方案總數。

【分析】

這個題目之所以是作為引例而不是例題,是因為它實在是個非常簡單的組合學問題:我們一行一行放置,則第一行有n種選擇,第二行n-1,……,最后一行只有1種選擇,根據乘法原理,答案就是n!。這里既然以它作為狀態壓縮的引例,當然不會是為了介紹組合數學。我們下面來看另外一種解法:狀態壓縮遞推(States Compressing Recursion,SCR)

我們仍然一行一行放置。取棋子的放置情況作為狀態,某一列如果已經放置棋子則為1,否則為0。這樣,一個狀態就可以用一個最多20位的二進制數表示。例如n=5,第1、3、4列已經放置,則這個狀態可以表示為01101(從右到左)。設f[s]為達到狀態s的方案數,則可以嘗試建立f的遞推關系。

考慮n=5,s=01101。這個狀態是怎么得到的呢?因為我們是一行一行放置的,所以當達到s時已經放到了第三行。又因為一行能且僅能放置一個車,所以我們知道狀態s一定來自:

①前兩行在第3、4列放置了棋子(不考慮順序,下同),第三行在第1列放置;②前兩行在第1、4列放置了棋子,第三行在第3列放置;

③前兩行在第1、3列放置了棋子,第三行在第4列放置。

這三種情況互不相交,且只可能有這三種情況。根據加法原理,f[s]應該等于這三種情況的和。寫成遞推式就是:

f[01101]=f[01100]+f[01001]+f[00101]

根據上面的討論思路推廣之,得到引例的解決辦法:

f[0]=1

f[s]=∑f[s^2i]

其中s∈[0…01,1…11],s的右起第i+1位為1。

?

反思這個算法,其正確性毋庸置疑(可以和n!對比驗證)。但是算法的時間復雜度為O(n2n),空間復雜度O(2n),是個指數級的算法,比循環計算n!差了好多,它有什么優勢?較大的推廣空間。

?

Sample Problems

【例1】

在n*n(n≤20)的方格棋盤上放置n個車,某些格子不能放,求使它們不能互相攻擊的方案總數。

【分析】

對于這個題目,如果組合數學學得不夠扎實,你是否還能一眼看出解法?應該很難。對于這個題目,確實存在數學方法(容斥原理),但因為和引例同樣的理由,這里不再贅述。

聯系引例的思路,發現我們并不需要對算法進行太大的改變。引例的算法是在枚舉當前行(即s中1的個數,設為r)的放置位置(即枚舉每個1),而對于例1,第r行可能存在無法放置的格子,怎么解決這個問題呢?枚舉1的時候判斷一下嘛!事實的確是這樣,枚舉1的時候判斷一下是否是不允許放置的格子即可。

但是對于n=20,O(n2n)的復雜度已經不允許我們再進行多余的判斷。所以實現這個算法時應該應用一些技巧。對于第r行,我們用a[r]表示不允許放置的情況,如果某一位不允許放置則為1,否則為0,這可以在讀入數據階段完成。運算時,對于狀態s,用tmps=s^a[r]來代替s進行枚舉,即不枚舉s中的1轉而枚舉tmps中的1。因為tmps保證了無法放置的位為0,這樣就可以不用多余的判斷來實現算法,代碼中只增加了計算a數組和r的部分,而時間復雜度沒有太大變化。

?

這樣,我們直接套用引例的算法就使得看上去更難的例1得到了解決。你可能會說,這題用容斥原理更快。沒錯,的確是這樣。但是,容斥原理在這題上只有當棋盤為正方形、放入的棋子個數為n、且棋盤上禁止放置的格子較少時才有簡單的形式和較快的速度。如果再對例1進行推廣,要在m*n的棋盤上放置k個車,那么容斥原理是無能為力的,而SCR算法只要進行很少的改變就可以解決問題。這也體現出了引例中給出的算法具有很大的擴展潛力。

?

棋盤模型是狀態壓縮最好的展示舞臺之一。下面再看幾個和棋盤有關的題目。

?

【例2】

給出一個n*m的棋盤(n、m≤80,n*m≤80),要在棋盤上放k(k≤20)個棋子,使得任意兩個棋子不相鄰。每次試驗隨機分配一種方案,求第一次出現合法方案時試驗的期望次數,答案用既約分數表示。

?

【分析】

顯然,本題中的期望次數應該為出現合法方案的概率的倒數,則問題轉化為求出現合法方案的概率。而概率=,方案總數顯然為C(n*m,k),則問題轉化為求合法方案數。整理一下,現在的問題是:在n*m的棋盤上放k個棋子,求使得任意兩個棋子不相鄰的放置方案數。

?

這個題目的狀態壓縮模型是比較隱蔽的。觀察題目給出的規模,n、m≤80,這個規模要想用SC是困難的,若同樣用上例的狀態表示方法(放則為1,不放為0),280無論在時間還是在空間上都無法承受。然而我們還看到n*m≤80,這種給出數據規模的方法是不多見的,有什么玄機呢?能把狀態數控制在可以承受的范圍嗎?稍微一思考,我們可以發現:9*9=81>80,即如果n,m都大于等于9,將不再滿足n*m≤80這一條件。所以,我們有n或m小于等于8,而28是可以承受的。我們假設m≤n(否則交換,由對稱性知結果不變)n是行數m是列數,則每行的狀態可以用m位的二進制數表示。但是本題和例1又有不同:例1每行每列都只能放置一個棋子,而本題卻只限制每行每列的棋子不相鄰。但是,上例中枚舉當前行的放置方案的做法依然可行。我們用數組s[1..num]保存一行中所有的num個放置方案,則s數組可以在預處理過程中用DFS求出,同時用c[i]保存第i個狀態中1的個數以避免重復計算。開始設計狀態。如注釋一所說,維數需要增加,原因在于并不是每一行只放一個棋子,也不是每一行都要求有棋子,原先的表示方法已經無法完整表達一個狀態。我們用f[i][j][k]表示第i行的狀態為s[j]且前i行已經放置了k個棋子的方案數。沿用枚舉當前行方案的做法,只要當前行的方案和上一行的方案不沖突即可,“微觀”地講,即s[snum[i]]和s[snum[i-1]]沒有同為1的位,其中snum[x]表示第x行的狀態的編號。然而,雖然我們枚舉了第i行的放置方案,但卻不知道其上一行(i-1)的方案。為了解決這個問題,我們不得不連第i-1的狀態一起枚舉,則可以寫出遞推式:

f[0][1][0]=1;

f[i][j][k]=∑f[i-1][p][k-c[j]]

其中s[1]=0,即在當前行不放置棋子;j和p是需要枚舉的兩個狀態編號,且要求s[j]與s[p]不沖突,即s[j]&s[p]=0。

當然,實現上仍有少許優化空間,例如第i行只和第i-1行有關,可以用滾動數組節省空間。

有了合法方案數,剩下的問題就不是很困難了,需要注意的就只有C(n*m,k)可能超出64位整數范圍的問題,這可以通過邊計算邊用GCD約分來解決,具體可以參考附件中的代碼。這個算法時間復雜度O(n*pn*num2),空間復雜度(滾動數組)O(pn*num),對于題目給定的規模是可以很快出解的。

?

?

?

通過上文的例題,讀者應該已經對狀態壓縮有了一些感性的認識。下面這個題目可以作為練習。

?

?

【例3】

在n*n(n≤10)的棋盤上放k個國王(可攻擊相鄰的8個格子),求使它們無法互相攻擊的方案數。

?

【分析】

其實有了前面幾個例子的分析,這個題目應該是可以獨立解決的。不過既然確實有疑問,那我們就來分析一下。

?

首先,你應該能想到將一行的狀態DFS出來(如果不能,請返回重新閱讀,謝謝),仍然設為s[1..num],同時仍然設有數組c[1..num]記錄狀態對應的1的個數。和例2相同,仍然以f[i][j][k]表示第i行狀態為s[j],且前i行已經放置了k個棋子的方案數。遞推式仍然可以寫作:

f[0][1][0]=1;

f[i][j][k]=∑f[i-1][p][k-c[j]]

其中仍要求s[j]和s[p]不沖突。

可是問題出來了:這題不但要求不能行、列相鄰,甚至不能對角線相鄰!s[j]、s[p]不沖突怎么“微觀地”表示呢?其實,稍微思考便可以得出方法:用s[p]分別和s[j]、s[j]*2、s[j]/2進行沖突判斷即可,原理很顯然。解決掉這唯一的問題,接下來的工作就沒有什么難度了。算法復雜度同例2。

?

下一個例題是狀態壓縮棋盤模型的經典題目,希望解決這個經典的題目能夠增長你的自信。

?

【例4】

給出一個n*m(n≤100,m≤10)的棋盤,一些格子不能放置棋子。求最多能在棋盤上放置多少個棋子,使得每一行每一列的任兩個棋子間至少有兩個空格。

【分析】

顯然,你應該已經有DFS搜出一行可能狀態的意識了(否則請重新閱讀之前的內容3遍,謝謝),依然設為s[1..num],依舊有c[1..num]保存s中1的個數,依照例1的預處理搞定不能放置棋子的格子。

問題是,這個題目的狀態怎么選?繼續像例2、3那樣似乎不行,原因在于棋子的攻擊范圍加大了。但是我們照葫蘆畫瓢:例2、3的攻擊范圍只有一格,所以我們的狀態中只需要有當前行的狀態即可進行遞推,而本題攻擊范圍是兩格,因此增加一維來表示上一行的狀態。用f[i][j][k]表示第i行狀態為s[j]、第i-1行狀態為s[k]時前i行至多能放置的棋子數,則狀態轉移方程很容易寫出:

f[i][j][k]=max{f[i-1][k][l]}+c[j]

其中要求s[j],s[k],s[l]互不沖突。

因為棋子攻擊范圍為兩格,可以直觀地想象到num不會很大。的確,由例2中得到的num的計算式并代入d=2、m=10,得到num=60。顯然算法時間復雜度為O(n*num3),空間復雜度(滾動數組)O(num2)。此算法還有優化空間。我們分別枚舉了三行的狀態,還需要對這三個狀態進行是否沖突的判斷,這勢必會重復枚舉到一些沖突的狀態組合。我們可以在計算出s[1..num]后算出哪些狀態可以分別作為兩行的狀態,這樣在DP時就不需要進行盲目的枚舉。這樣修改后的算法理論上比上述算法更優,但因為num本身很小,所以這樣修改沒有顯著地減少運行時間。值得一提的是,本題筆者的算法雖然在理論上并不是最優,但由于位運算的使用,截至2月9日,筆者的程序在PKU OJ上長度最短,速度第二快。

這個題目是國內比賽中較早出現的狀態壓縮題。它告訴我們狀態壓縮不僅可以像前幾個例題那樣求方案數,而且可以求最優方案,即狀態壓縮思想既可以應用到遞推上(SCR),又可以應用到DP上(SCDP),更說明其有廣泛的應用空間。

?

?

看了這么多棋盤模型應用狀態壓縮的實例,你可能會有疑問,難道狀態壓縮只在棋盤上放棋子的題目中有用?不是的。我們暫時轉移視線,來看看狀態壓縮在其他地方的應用——覆蓋模型

?

【例5】

給出n*m (1≤n、m≤11)的方格棋盤,用1*2的長方形骨牌不重疊地覆蓋這個棋盤,求覆蓋滿的方案數。

?

【分析】

這也是個經典的組合數學問題:多米諾骨牌完美覆蓋問題(或所謂二聚物問題)。有很多關于這個問題的結論,甚至還有個專門的公式:如果m、n中至少有一個是偶數,則結果=。這個公式形式比較簡單,且計算的復雜度是O()的,很高效。但是這個公式內還有三角函數,且中學生幾乎不可能理解,所以對我們能力的提高沒有任何幫助。用SCR算法能較好地解決這個問題。

顯然,如果n、m都是奇數則無解(由棋盤面積的奇偶性知),否則必然有至少一個解(很容易構造出),所以假設n、m至少有一個偶數,且m≤n(否則交換)。我們依然像前面的例題一樣把每行的放置方案DFS出來,逐行計算。用f[i][s]表示把前i-1行覆蓋滿、第i行覆蓋狀態為s的覆蓋方案數。因為在第i行上放置的骨牌最多也只能影響到第i-1行,則容易得遞推式:

f[0][1…11]=1

f[i][s1]=∑f[i-1][s2]

其中(s1,s2)整體作為一個放置方案,可以把所有方案DFS預處理出來。下面討論一下本題的一些細節。

首先討論DFS的一些細節。對于當前行每一個位置,我們有3種放置方法:①豎直覆蓋,占據當前格和上一行同一列的格;②水平覆蓋,占據當前格和該行下一格;③不放置骨牌,直接空格。如何根據這些枚舉出每個(s1,s2)呢?下面介紹兩種方法:

第一種:

DFS共5個參數,分別為:p(當前列號),s1、s2(當前行和上一行的覆蓋情況),b1、b2(上一列的放置對當前列上下兩行的影響,影響為1否則為0)。初始時s1=s2=b1=b2=0。①p=p+1,s1=s1*2+1,s2=s2*2,b1=b2=0;②p=p+1,s1=s1*2+1,s2=s2*2+1,b1=1,b2=0;③p=p+1,s1=s1*2,s2=s2*2+1,b1=b2=0。當p移出邊界且b1=b2=0時記錄此方案。

?

第二種:

觀察第一種方法,發現b2始終為0,知這種方法有一定的冗余。換個更自然的方法,去掉參數b1、b2。①p=p+1,s1=s1*2+1,s2=s2*2;②p=p+2,s1=s1*4+3,s2=s2*4+3;③p=p+1,s1=s1*2,s2=s2*2+1。當p移出邊界時記錄此方案。這樣,我們通過改變p的移動距離成功簡化了DFS過程,而且這種方法更加自然。

?

DFS過程有了,實現方法卻還有值得討論的地方。前面的例題中,我們為什么總是把放置方案DFS預處理保存起來?是因為不合法的狀態太多,每次都重新DFS太浪費時間。然而回到這個題目,特別是當采用第二種時,我們的DFS過程中甚至只有一個判斷(遞歸邊界),說明根本沒有多少不合法的方案,也就沒有必要把所有方案保存下來,對于每行都重新DFS即可,這不會增加運行時間卻可以節省一些內存。

?

這個算法時間復雜度為多少呢?因為DFS時以兩行為對象,每行2m,共進行n次DFS,所以是O(n*4m)?根據“O”的上界意義來看并沒有錯,但這個界并不十分精確,也可能會使人誤以為本算法無法通過1≤n、m≤11的測試數據,而實際上本算法可以瞬間給出m=10,n=11時的解。為了計算精確的復雜度,必須先算出DFS得到的方案數。

考慮當前行的放置情況。如果每格只有①③兩個選擇,則應該有2m種放置方案;如果每格有①②③這3個選擇,且②中p只移動一格,則應該有3m種放置方案。然而現在的事實是:每格有①②③這3個選擇,但②中p移動2格,所以可以知道方案數應該在2m和3m之間。考慮第i列,則其必然是:第i-1列采用①③達到;第i-2列采用②達到。設h[i]表示前i列的方案數,則得到h[i]的遞推式:

h[0]=1,h[1]=2

h[i]=2*h[i-1]+h[i-2]

應用組合數學方法求得其通項公式h[m]=。注意到式子的第二項是多個絕對值小于1的數的乘積,其對整個h[m]的影響甚小,故略去,得到方案數h[m]≈0.85*2.414m,符合2m<h[m]<3m的預想。

?

因為總共進行了n次DFS,每次復雜度為O(h[m]),所以算法總時間復雜度為O(n*h[m])=O(n*0.85*2.414m),對m=10,n=11不超時也就不足為奇了。應用滾動數組,空間復雜度為O(2m)。

?

?

對于本題,我們已經有了公式和SCR兩種算法。公式對于m*n不是很大的情況有效,SCR算法在競賽中記不住公式時對小的m、n有效。如果棋盤規模為n*m(m≤10,n≤231-1),則公式和SCR都會嚴重超時。有沒有一個算法能在1分鐘內解決問題呢?答案是肯定的,它仍然用到SC思想。

?

此算法中應用到一個結論:給出一個圖的鄰接矩陣G(允許有自環,兩點間允許有多條路徑,此時G[i][j]表示i到j的邊的條數),則從某點a走k步到某點b的路徑數為Gk[a][b]。本結論實際上是通過遞推得到的,簡單證明如下:從i走k步到j,必然是從i走k-1步到t,然后從t走1步到j,根據加法原理,即G[k][i][j]=∑G[k-1][i][t]*G[t][j]。是否感到這個式子很眼熟?沒錯,它和矩陣乘法一模一樣,即:G[k]=G[k-1]*G。因為矩陣乘法滿足結合律,又由G[1]=G,所以我們得到結果:G[k]=Gk

下面介紹這個算法。考慮一個有2m個頂點的圖,每個頂點表示一行的覆蓋狀態,即SCR算法中的s1或s2。如果(s1,s2)為一個放置方案,則在s2和s1之間連一條(有向)邊,則我們通過DFS一次可以得到一個鄰接矩陣G。仍然按照逐行放置的思想來考慮,則要求我們每行選擇一個覆蓋狀態,且相鄰兩行的覆蓋狀態(s1,s2)應為一個放置方案,一共有n行,則要求選擇n個狀態,在圖中考慮,則要求我們從初始(第0行)頂點(1...111)n步走到(1…111),因為圖的鄰接矩陣是DFS出來的,每條邊都對應一個放置方案,所以可以保證走的每條邊都合法。因此,我們要求的就是頂點(1…111)走n步到達(1…111)的路徑條數。由上面的結論知,本題的答案就是Gn[1…111][1…111]。

現在的問題是,如何計算G的n次冪?連續O(n)次矩陣乘法嗎?不可取。矩陣的規模是2m*2m,一次普通矩陣乘法要O((2m)3)=O(8m),O(n)次就是O(n*8m),比SCR算法還要差得多。其實我們可以借用二分的思想。如果要計算38的值,你會怎么算呢?直接累乘將需要進行7次乘法。一種較簡單的方法是:3*3=32,32*32=34,34*34=38,只進行了3次乘法,效率高了許多。因為矩陣乘法滿足結合律,所以可以用同樣的思路進行優化。這種思路用遞歸來實現是非常自然的,然而,本題的矩陣中可能有210*210=220=1048576個元素,如果用(未經優化的)遞歸來實現,將可能出現堆棧溢出。不過慶幸的是我們可以非遞歸實現。用bin[]保存n的二進制的每一位,從最高位、矩陣G開始,如果bin[當前位]為0,則把上一位得到的矩陣平方;如果為1,則平方后再乘以G。這種方法的時間復雜度容易算出:O(logn) 次矩陣乘法,每次O(8m),共O(8m*logn)。

這樣對于m≤7就可以很快出解了。但對于m=n=8,上述算法都需要1s才能出解,無法令人滿意。此算法還有優化空間。

我們的矩陣規模高達2m*2m=4m,但是其中有用的(非0的)有多少個呢?根據介紹SCR算法時得到的h[m]計算式,G中有4m-h[m]=4m-0.85*2.414m個0,對于m=8,可以算出G中98.5%的元素都是0,這是一個非常非常稀疏的矩陣,使用三次方的矩陣乘法有點大材小用。我們改變矩陣的存儲結構,即第p行第q列的值為value的元素可以用一個三元組(p,q,value)來表示,采用一個線性表依行列順序來存儲這些非0元素。怎樣對這樣的矩陣進行乘法呢?觀察矩陣乘法的計算式,當a[i][k]或者b[k][j]為0時,結果為0,對結果沒有影響,完全可以略去這種沒有意義的運算。則得到計算稀疏矩陣乘法的算法:枚舉a中的非0元素,設為(p,q,v1),在b中尋找所有行號為q的非0元素(q,r,v2),并把v1*v2的值累加到c[p][r]中。這個算法多次用到一個操作:找出所有行號為q的元素,則可以給矩陣附加一個數組hp[q],表示線性表中第一個行號為q的元素的位置,若不存在則hp[q]=0。算出二維數組c之后再對其進行壓縮存儲即可。此矩陣乘法的時間復雜度為O(),在最壞情況下,a.not0=b.not0=4m,算法的復雜度為O(8m),和經典算法相同。因為矩陣非常稀疏,算法復雜度近似為O(4m)?。考慮整個算法的時間復雜度:O(logn)次矩陣乘法,每次O(4m),則總時間復雜度O(logn*4m),對于m≤9也可以很快出解了,對于m=10,n=2147483647,此算法在筆者機器上(Pm 1.6G,512M)運行時間少于20s。雖然仍然不夠理想,但已經不再超時數小時。此算法空間復雜度為O(max_not0+4m),對于m=10,max_not0小于190000。

?

?

以上給出了公式、SCR、矩陣乘方這3個算法,分別適用于不同的情況,本題基本解決。

?

?

?

讀者應該已經注意到,覆蓋模型和棋盤模型有很多共同點,譬如都是在矩形某些位置放入棋子(或某種形狀的骨牌)來求方案數(如上例)或最優解(下面將會給出幾個例題)。但不難看出,覆蓋模型和棋盤模型又有著很大的不同:棋盤模型中,只要棋子所在的位置不被別的棋子攻擊到即可,而覆蓋模型中,棋子的攻擊范圍也不可以重疊。所以簡單來說,覆蓋模型就是攻擊范圍也不能重疊的棋盤模型。下面再給出一個與上例類似的覆蓋模型的例題以加深印象。

?

?

?

【例6】

給出n*m (1≤n、m≤9)的方格棋盤,用1*2的矩形的骨牌和L形的(2*2的去掉一個角)骨牌不重疊地覆蓋,求覆蓋滿的方案數。

?

【分析】

觀察題目條件,只不過是比例5多了一種L形的骨牌,因此很自然地順著例5的思路走。本題中兩種骨牌的最大長度和例5一樣,所以仍然用f[i][s]表示把前i-1行覆蓋滿、第i行覆蓋狀態為s的覆蓋方案數,得到的遞推式和例5完全一樣:

f[0][1…11]=1

f[i][s1]=∑f[i-1][s2]

其中(s1,s2)整體作為一個放置方案。例5中有兩種DFS方案,其中第二種實現起來較第一種簡單。但在本題中,新增的L形骨牌讓第二種DFS難以實現,在例5中看起來有些笨拙的第一種DFS方案在本題卻可以派上用場。回顧第一種DFS,我們有5個參數,分別為:p(當前列號),s1、s2(當前行和對應的上一行的覆蓋情況),b1、b2(上一列的放置對當前列兩行的影響,影響為1否則為0)。本題中,可選擇的方案增多,故列表給出:

?

?

覆蓋情況

條件

參數s變化

參數b變化

1

0 0

0 0

s1=s1*2+b1

s2=s2*2+1-b2

b1=0

b2=0

2

0 0

1 1

b1=0

s1=s1*2+1

s2=s2*2+1-b2

b1=1

b2=0

3

1 0

1 0

b1=0

b2=0

s1=s1*2+1

s2=s2*2

b1=0

b2=0

4

1 0

1 1

b1=0

b2=0

s1=s1*2+1

s2=s2*2

b1=1

b2=0

5

0 1

1 1

b1=0

s1=s1*2+1

s2=s2*2+1-b2

b1=1

b2=1

6

1 1

0 1

b2=0

s1=s1*2+b1

s2=s2*2

b1=1

b2=1

7

1 1

1 0

b1=0

b2=0

s1=s1*2+1

s2=s2*2

b1=0

b2=1

?

容易看出,在本題中此種DFS方式實現很簡單。考慮其復雜度,因為L形骨牌不太規則,筆者沒能找到一維的方案數的遞推公式,因此無法給出復雜度的解析式。但當m=9時,算法共生成放置方案79248個,則對于n=m=9,算法的復雜度為O(9*79248),可以瞬間出解。和上例一樣,本題也沒有必要保存所有放置方案,也避免MLE。

?

那么,對于本題是否可以應用上題的矩陣算法呢?答案是肯定的,方法也類似,復雜度為O(8m*logn)。然而,對于本題卻不能通過上題的稀疏矩陣算法加速,原因在于剛開始時矩陣中只有1-79248/49=70%的0,而運算結束后整個矩陣中只有2個0,根本無法達到加速效果。

?

由于有上題的鋪墊,基本相同的本題也很快得到了解決。

?

?

【例7】

給出n*m(n,m≤10)的方格棋盤,用1*r的長方形骨牌不重疊地覆蓋這個棋盤,求覆蓋滿的方案數。

【分析】

本題是例5的直接擴展。如果說例5中公式比SCR好,本題可以指出當公式未知時SCR依然是可行的算法。直接思考不容易發現方法,我們先考慮r=3時的情況。首先,此問題有解當且僅當m或n能被3整除。更一般的結論是:用1*r的骨牌覆蓋滿m*n的棋盤,則問題有解當且僅當m或n能被r整除。當r=2時,則對應于例5中m、n至少有一個是偶數的條件。此結論的組合學證明從略。

不同于例5,1*3骨牌的“攻擊范圍”已經達到了3行,可以想象例5中的表示方法已經無法正確表示所有狀態,但其思路依然可以沿用。例5中用f[i][s]表示把前i-1行覆蓋滿、第i行覆蓋狀態為s的覆蓋方案數,是因為當前行的放置方案至多能影響到上一行,狀態中只要包含一行的覆蓋狀態即可消除后效性。本題中當前行的放置方案可以影響到上兩行,故可以想到應保存兩行的覆蓋狀態以消除后效性,即增加一維,用f[i][s1][s2]表示把前i-2行覆蓋滿、第i-1行覆蓋狀態為s1、第i行覆蓋狀態為s2的覆蓋方案數。先不論上述表示方法是否可行(答案是肯定的),r=2時狀態有2維,r=3時有3維,推廣后狀態變量居然有r維,這樣的方法不具有推廣價值,而且空間復雜度也太高。

仔細分析上述方案,可以發現其失敗之處。s1的第p位s1p為1(覆蓋)時,s2p是不可能為0的(要求覆蓋滿),則這兩位(s1p, s2p)的(0,0),(0,1),(1,0),(1,1)四種組合中有一種不合法,而上述狀態表示方法卻冗余地保存了這個組合,造成空間復雜度過高,也進行了多余的計算。通過上面的討論可以知道,每一位只有3種狀態,引導我們使用三進制。我們用f[i][s]表示把前i-2行覆蓋滿、第i-1和第i行覆蓋狀態為s的覆蓋方案數,但這里狀態s不再是二進制,而是三進制:sp=0表示s1p=s2p=0;sp=1表示s1p=0,s2p=1;sp=2表示s1p=s2p=1。這樣,我們就只保留了必要的狀態,空間和時間上都有了改進。當r=4時,可以類推,用四進制表示三行的狀態,r=5時用五進制……分別寫出r=2,3,4,5的程序,進行歸納,統一DFS的形式,可以把DFS(p,s1,s2)分為兩部分:①for i=0 to r-1 do DFS(p+1,s1*r+i,s2*r+(i+1)mod r);②DFS(p+r,s1*rr+rr-1,s2*rr+rr-1) 問題解決。但DFS的這種分部方法是我們歸納猜想得到的,并沒有什么道理,其正確性無法保證,我們能否通過某種途徑證明它的正確性呢?仍以r=3為例。根據上面的討論,sp取值0到2,表示兩行第p位的狀態,但sp并沒有明確的定義。我們定義sp為這兩行的第p位從上面一行開始向下連續的1的個數,這樣的定義可以很容易地遞推,遞推式同上兩例沒有任何改變,卻使得上述DFS方法變得很自然。

分析算法的時間復雜度,同例5一樣需要用到DFS出的方案個數h[m],并且仿照例5中h[m]的遞推式,我們可以得到:

h[i]=ri?(i=0~r-1)

h[j]=r*h[j-1]+h[j-r] (j=r~m)

理論上我們可以根據遞推式得到例5中那樣的精確的通項公式,但需要解高于三次的方程,且根多數為復數,無法得到例5那樣簡單優美的表達式,這里僅給出r=2..5,m=10時h[m]的值依次為:5741,77772,1077334,2609585,9784376。對于推廣后的問題,例5的矩陣算法依然可行,但此時空間將是一個瓶頸。

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/445441.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/445441.shtml
英文地址,請注明出處:http://en.pswp.cn/news/445441.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

數據結構和算法(06)---二叉樹(c++)

文章目錄目錄二叉樹1.二叉樹的基本概念2.二叉樹的應用和時間復雜度3.二叉樹的插入4.二叉樹的查找5. 二叉樹的遍歷6.二叉樹的刪除二叉樹的基本操作1.二叉樹的基礎操作2.代碼實現創建二叉樹和三種遍歷二叉樹的方法目錄 數據結構&#xff1a; 邏輯結構&#xff1a;數組&#xff0c…

如何轉載CSDN博客

前言 對于喜歡逛CSDN的人來說&#xff0c;看別人的博客確實能夠對自己有不小的提高&#xff0c;有時候看到特別好的博客想轉載下載&#xff0c;但是不能一個字一個字的敲了&#xff0c;這時候我們就想快速轉載別人的博客&#xff0c;把別人的博客移到自己的空間里面&#xff0c…

程序員歌曲推薦

更多的時候&#xff0c;不光是電腦和鍵盤陪著我們度過一天&#xff0c;耳機和音樂也成了IT人生活中必不可少的一部分 上班和下班的路上&#xff0c;心情失落時&#xff0c;失眠時&#xff0c;甚至是工作時都可能會聽音樂&#xff0c;讓音樂為我們療傷&#xff0c;讓精神得以放…

如何在博客內添加音樂

<center> <iframe border"0" width"480" height"640" src"//music.163.com/outchain/player?type0&amp;id448977181;auto1&amp;height430"> </iframe> </center> 將代碼復制到markdown編輯器里&am…

CSDN寫博客(字體顏色、大小)

markdown里面的標記語言可以使用標簽對來實現對文本文字顏色大小信息的控制。下面給出幾個實例&#xff1a; 黑體字示例 微軟雅黑示例 華文彩云示例 color#00ffff size可以根據實際大小進行設置&#xff0c;一般不超過7。 紅色字體CSDN 紅色字體CSDN 使用十六進制顏色值 …

bose qc30 安靜的城市是什么樣子

使用感受 網友1&#xff08;20歲&#xff09;&#xff1a; 當你帶著這個耳機聽音樂的時候&#xff0c;有一種感覺&#xff0c;感覺這個世界都是你歌曲里的MV&#xff0c;這個枯燥乏味的世界都被賦予了你心中的那份情感&#xff0c;這種感覺&#xff0c;真的很棒 網友2&#…

DeepLearning.ai 提煉筆記(5-1)-- 循環神經網絡

參考博客 Class 5: 序列模型Sequence Models Week 1: 循環神經網絡RNN (Recurrent) 文章目錄Class 5: 序列模型Sequence ModelsWeek 1: 循環神經網絡RNN (Recurrent)目錄序列模型-循環神經網絡1.序列模型的應用2.數學符號3.循環神經網絡模型傳統標準的神經網絡循環神經網絡的…

常見人工智能比賽平臺總結

目錄1.kaggle比賽1.1 kaggle比賽是什么&#xff1f;1.2 為什么舉辦kaggle比賽&#xff1f;1.3 kaggle比賽形式是什么&#xff1f;1.4 kaggle比賽的獎勵制度是什么&#xff1f;2.阿里天池比賽2.1 阿里天池比賽是什么&#xff1f;2.2 為什么舉辦阿里天池比賽&#xff1f;2.3 阿里…

機器學習模型評分總結(sklearn)

文章目錄目錄模型評估評價指標1.分類評價指標acc、recall、F1、混淆矩陣、分類綜合報告1.準確率方式一&#xff1a;accuracy_score方式二&#xff1a;metrics2.召回率3.F1分數4.混淆矩陣5.分類報告6.kappa scoreROC1.ROC計算2.ROC曲線3.具體實例2.回歸評價指標3.聚類評價指標1.…

kaggle (02) - 房價預測案例(進階版)

房價預測案例&#xff08;進階版&#xff09; 這是進階版的notebook。主要是為了比較幾種模型框架。所以前面的特征工程部分內容&#xff0c;我也并沒有做任何改動&#xff0c;重點都在后面的模型建造section Step 1: 檢視源數據集 import numpy as np import pandas as pd讀…

《Head First設計模式》第二章筆記 觀察者模式

背景 客戶有一個WeatherData對象&#xff0c;負責追蹤溫度、濕度和氣壓等數據。現在客戶給我們提了個需求&#xff0c;讓我們利用WeatherData對象取得數據&#xff0c;并更新三個布告板&#xff1a;目前狀況、氣象統計和天氣預報。 WeatherData對象提供了4個接口&#xff1a; …

libsvm總結

1. 訓練 格式&#xff1a;model libsvmtrain(training_label_vector, training_instance_matrix [, libsvm_options]); 這個函數有三個參數&#xff0c;其中 -training_label_vector:訓練樣本的類標&#xff0c;如果有m個樣本&#xff0c;就是m x 1的矩陣&#xff08;類型必須…

《Head First設計模式》第三章筆記 裝飾者模式

裝飾者模式&#xff08;Decorator Pattern) *利用組合&#xff08;composition&#xff09;和委托&#xff08;delegation&#xff09;可以在運行時實現繼承行為的效果&#xff0c;動態地給對象加上新的行為。 *利用繼承擴展子類的行為&#xff0c;是在編譯時靜態決定的&#x…

機器學習中如何解決數據不平衡問題?

文章目錄目錄什么是數據不平衡問題&#xff1f;數據不平衡會造成什么影響&#xff1f;如何處理數據不平衡問題&#xff1f;1、重新采樣訓練集1.1隨機欠抽樣1.2.基于聚類的過采樣2.使用K-fold交叉驗證3.轉化為一分類問題4.組合不同的重采樣數據集5.用不同比例重新采樣6.多模型Ba…

《Head First設計模式》第四章筆記 工廠模式

之前我們一直在使用new操作符&#xff0c;但是實例化這種行為并不應該總是公開的進行&#xff0c;而且初始化經常會造成耦合問題&#xff0c;工廠模式將擺脫這種復雜的依賴&#xff0c;本次內容包括簡單工廠&#xff0c;工廠方法和抽象工廠三種情況。 1 2 3 4 5 6 Duck duck&a…

《Head First設計模式》第五章筆記-單件模式

單件模式 定義&#xff1a;確保一個類只有一個實例&#xff0c;并提供全局訪問點。 編寫格式&#xff1a; 1 2 3 4 5 6 public class MyClass{ private MyClass(){}//構造方法私有化 public static MyClass getInstance(){ //提供全局訪問點 return new My…

處理機器學習大數據的7種方法

文章目錄目錄1.分配更多的內存2.使用較小的樣本3.將數據提交至服務器上4.更改數據格式5.使用數據流方式或者逐行讀入的方法6.使用關系數據庫7.使用大數據平臺目錄 在實際的生產過程中&#xff0c;我們經常會遇到數據文件太大&#xff0c;而無法直接讀入到計算機中進行處理&…

《Head First設計模式》第六章筆記-命令模式

封裝調用-命令模式 命令模式可將“動作的請求者”從“動作的執行者”對象中解耦。 本篇中將不再描述書中所引入的“巴斯特家電自動化公司”的遙控器控制案例&#xff0c;而使用簡單易懂的餐廳案例。 在開始之前&#xff0c;讓我們通過一個現實中的例子來了解命令模式。 理解…

kaggle(04)---avazu_ctr_predictor(baseline)

比賽的目的&#xff1a; 通過分析網上的系統日志和用戶行為信息&#xff0c;來預測某些網頁上項目的點擊率。是一個二分類的問題&#xff0c;只需要預測出用戶是否點擊即可最好能夠輸出某個概率&#xff0c;比如&#xff1a;用戶點擊某個廣告的概率。 比賽官網 文件信息&…

一文讀懂機器學習庫graphLab

文章目錄目錄什么是graphlab為什么使用graphlab?如何安裝graphlab?graphlab的簡單使用。目錄 什么是graphlab GraphLab 是由CMU&#xff08;卡內基梅隆大學&#xff09;的Select 實驗室在2010 年提出的一個基于圖像處理模型的開源圖計算框架&#xff0c;框架使用C語言開發實…