木塊砌墻題目:
用三種木塊,搭建k×2n×1的墻,不能翻轉、旋轉木塊(0≤ n≤ 1024 ,1 ≤ k ≤ 5),計算有多少種方案,輸出結果對1000000007取模。
圖1 木塊與墻
?
該題目是在龐果網(http://hero.pongo.cn/)第一次看到,但是由于該網站的特殊之處,現在看不到了,不過可以在這看到http://tieba.baidu.com/p/2351476089。
?
在解題之前,首先感謝龐果網的熱心網友們的激情討論,才讓我的思路漸漸清晰成熟起來。
?
看到n可以取值1024,即21024,這是一個很龐大的數字,一般不會也不可能計算出該數據再做處理,正常的思路是要想辦法減低問題規模,并找出大規模問題與小規模問題之間的關系。該題目可以采用動態規劃算法,將原問題遞歸劃分為子問題,依據邊界條件計算出子問題的解,然后根據子問題的狀態及子問題之間的狀態轉移關系組合出原問題解。
墻與木塊的厚度都為1,則可以只考慮寬度和高度(k×2n),進一步降低思考的難度。假設該問題結果用F(n,k)表示,由題目可知墻的寬度隨著n增加1而增加一倍,所以降低n則可以快速降低問題規模。若能夠找出最優子結構滿足F(n,k) = g(F(n-1,k)); g()為子問題根據狀態即狀態轉移關系合并子問題的策略。
為了精確描述子問題,采用f(n,left,right)來表示不同狀態的子問題(如下圖所示)。由于木塊寬度最大為2,所以只有相鄰的兩列才可能有聯系,由上圖也可以看出,相鄰兩列最多有2k種聯系,即相鄰子問題合并時有2k狀態及相應的轉移操作,f(n,left,right) =∑f(n-1,left,i)*f(n-1,i,right) 。
? 但是子問題規模降低到足夠小,即n=0時到達邊界,這是種比較特殊的子問題,需要特殊處理:
n=0時,問題退化為只有一列,此時結果只與k有關,假設結果為h(k),若k增加1:
第k+1個木塊獨立存在時,共有h(k)種方案
第k+1個木塊與第k個木塊組個存在時,有h(k-1)種方案
所以h(k+1) = h(k) + h(k-1)種方案,即h(k)為斐波那契數列。
?
n=1也是比較特殊的子問題。比如,f(1,4,1)= f(0,4,4)*f(0,1,1)+f(0,6,6)*f(0,3,3),如下圖所示:
左右兩邊的狀態實質上是兩列之間有1*2的木塊,方便描述和計算才給這些被1*2木塊占據的位置編號作為子問題的狀態,左右狀態發生變化只能在沒有被占據的位置上增加,如上圖中f(1,4,1)的左狀態由4增加時只能增加到6,而不能是5,右狀態也是如此,只能增加到3。具體細節請看代碼實現。
#include <iostream>using namespace std;#ifdef WIN32
#define ll __int64
#else
#define ll long long
#endifconst unsigned long MOD = 1000000007;//斐波那契數列求解函數
unsigned long fb(int k){switch(k){case 0:case 1:return 1;case 2:return 2;case 3:return 3;default:unsigned long a = 2;unsigned long b = 3;unsigned long sum;for(int index=3;index!=k;++index){sum = a+b;a = b;b = sum;}return sum;}
}unsigned long calculate(int n,int k){
//子問題n=0時,結果退化為斐波那契額數列,特殊處理if(0 == n){return fb(k);}long top = 1<<k;//申請輔助空間,保存子問題結果ll ***res = new ll**[n+1];for(int i=0;i<n+1;++i){res[i] = new ll*[top];for(int j=0;j<top;++j){res[i][j] = new ll[top];}}//初始化子問題n=0時的結果。
//n=0時,子問題只有一列,沒有左右兩半,這里采用了一個技巧,將左右兩半看做一樣,所以初始化就少了很多。 for(int i=0;i!=top;++i){switch(i){case 0: res[0][i][i] = fb(k); break;case 1: res[0][i][i] = fb(k-1); break;case 2:case 3:res[0][i][i] = fb(k-2);break;case 4:res[0][i][i] = 2*fb(k-3);break;case 5:case 6:case 7:res[0][i][i] = fb(k-3);break;case 8:res[0][i][i] = 3*fb(k-4);break;case 9:res[0][i][i] = 2*fb(k-4);break;case 10: case 11:res[0][i][i] = fb(k-4);break;case 12:res[0][i][i] = 2*fb(k-4);break;case 13: case 14: case 15:res[0][i][i] = fb(k-4);break;default:break;} } //處理子問題n=1,這個子問題也比較特殊,左右狀態導致有n=0合并結果的特殊化 for(int left=0;left!=top;++left){for(int right=left;right!=top;++right){res[1][left][right] = 0;for(int stat=0;stat!=top;++stat){int tmp = stat -left + right;if(((stat&left)== left) && ((tmp&right) == right)){res[1][left][right]+= res[0][stat][stat]*res[0][tmp][tmp];} } res[1][right][left] =res[1][left][right];} } //處理正常子問題for(int row=2;row!=(n+1);++row){for(int left=0;left!=top;++left){for(int right=left;right!=top;++right){res[row][left][right]= 0;for(int stat=0;stat!=top;++stat){res[row][left][right]+= res[row-1][stat][right]*res[row-1][left][stat];res[row][left][right]%= MOD;} res[row][right][left]= res[row][left][right];} }}//返回結果return res[n][0][0];
}int main() {const int n[] = {0,32,65,105,207,311,425,579,999};const int k[] = {1,2,4,3,4,4,2,4,1};const int answer[] = {1,751648137,558333535,811740953,785867720,246173916,906082702,853581641,222787673};int i;bool flag = false;for (i = 0; i < 9; ++i) {if (calculate(n[i],k[i]) == answer[i]) {flag = true;}else {flag = false;break;}}if(flag){cout<<"YES!\n";}else{cout<<"NO\n";cout<<"the test data"<<n[i]<<" "<<k[i]<<endl;}return 0;
}
總結
以上是生活随笔為你收集整理的通过木块砌墙题目体会动态规划算法的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。