動態規劃系列專題講義之斐波那契數列
動態規劃系列專題講義
專題一:斐波那契數列
/*
Name: 動態規劃專題之斐波那契數列
Copyright: 巧若拙
Author:
Date: 22-03-17 08:56
Description: 1755_菲波那契數列
描述:斐波那契數列是指這樣的數列: 數列的第一個和第二個數都為1,接下來每個數都等於前面2個數之和。給出一個正整數a,要求斐波那契數列中第a個數是多少。
輸入:第1行是測試資料的組數n,後面跟著n行輸入。每組測試資料佔1行,包括一個正整數a(1<= a <= 20)
輸出:輸出有n行,每行輸出對應一個輸入。輸出應是一個正整數,為菲波那契數列中第a個數的大小
樣例輸入
4
5
2
19
1
樣例輸出
5
1
4181
1
*/
#include<iostream>
#include<cmath>
using namespace std;
const int MAXN = 50;
int F1[MAXN];//Fibonacci數列
int F2[MAXN] = {0, 1};//Fibonacci數列
int Fibonacci(int n); //遞迴演算法
int Fibonacci_1(int n); //備忘錄:自頂而下
int Fibonacci_2(int n);//動態規劃:自底而上
int Fibonacci_3(int n);//動態規劃:降維優化
int main()
{
int n, a;
Fibonacci_2(MAXN);//動態規劃,先記錄所有子問題的解
cin>> n;
for(int i=0; i<n; i++)
{
cin>> a;
cout << Fibonacci(a) << endl;
cout << Fibonacci_1(a) << endl;
cout << F2[a] << endl;
cout << Fibonacci_3(a) <<endl;
}
return0;
}
演算法1:遞迴演算法,沒有記錄任何中間結果。
int Fibonacci(int n)
{
if (n == 0 || n == 1) //遞迴出口
{
return //語句1
}
return Fibonacci( ) + Fibonacci( ); //語句2
}
問題1:將語句1和語句2補充完整。
參考答案:
問題1:語句1:return n;
語句2:return Fibonacci(n-1) +Fibonacci(n-2);
演算法2:備忘錄演算法:自頂而下,需要用到全域性變數F1 [MAXN]。
int Fibonacci_1(int n)
{
if (F1[n] > 0) //如果這個問題曾經計算過,直接返回
{
return //語句1
}
if(n == 0 || n == 1) //遞迴出口
{
F1[n] = //語句2
}
else
{
F1[n]= //語句3
}
return F1[n];
}
問題1:將語句1,語句2和語句3補充完整。
問題2:與演算法1(遞迴演算法)相比,演算法2(備忘錄演算法)有哪些優越之處?
參考答案:
問題1:語句1:return F1[n];
語句2:F1[n] = n;
語句3:F1[n] = Fibonacci_1(n-1) + Fibonacci_1(n-2);
問題2:遞迴演算法進行了重複計算,而備忘錄演算法利用一維陣列F1[n]記錄了子問題的解,無需重複計算,大大提高了效率。
演算法3:動態規劃:自底而上,需要用到全域性變數int F2[MAXN] ={0, 1};。
int Fibonacci_2(int n)
{
for (int i=2; i<=n; i++)
{
F2[i] = //語句1
}
return F2[n];
}
問題1:將語句1補充完整。
問題2:與演算法2(備忘錄演算法)相比,演算法3(動態規劃)有哪些異同?
參考答案:
問題1:語句1:F2[i] = F2[i-1] + F2[i-2];
問題2:備忘錄和動態規劃演算法都是利用遞推表示式獲得子問題的解,並記錄了子問題的解,用空間換時間,提高了時間效率。但是二者的思考方向不同,備忘錄演算法是自頂而下,從最終解出發,採用遞迴的方式來求解;動態規劃演算法是自底而上,從最小的子問題出發,逐步向上求出較大問題的解,直到獲得最終解。
演算法4:/動態規劃:降維優化,使用3個變數代替一維陣列。
int Fibonacci_3(int n)
{
intcur, pre1, pre2;
pre1= 0, cur = pre2 = 1; //初始化
for (int i=2; i<=n; i++) //自底向上,迭代更新變數值
{
cur= //語句1
pre1 = //語句2
pre2 = //語句3
}
return cur;
}
問題1:將語句1,語句2和語句3補充完整。
問題2:與演算法3(基本動態規劃演算法)相比,演算法4(動態規劃降維優化)有哪些異同?
參考答案:
問題1:語句1:cur = pre1 + pre2;
語句2:pre1 = pre2;
語句3:pre2 = cur;
問題2:二者同屬動態規劃演算法,都利用額外的變數(或陣列)記錄了各個子問題的解,都是從最小的子問題出發,逐步向上求出較大問題的解,直到獲得最終解。演算法3保留了所有子問題的解,雖然需要較多的空間,但是一次計算之後,就可以直接輸出任意解了;演算法4利用斐波那契數列遞推公式的特性,只保留了每個元素的當前值和它前面的2個元素值,對計算過程中產生的子問題解用過即棄,所需空間較少,但每次求解新元素的值,都需要從頭開始計算,適用於只需要求某一個元素值的情形。
課後練習:
練習1:1788_Pell數列
描述:Pell數列a1, a2, a3, ...的定義是這樣的,a1 = 1, a2 = 2, ... , an = 2* an-1 + an - 2 (n > 2)。
給出一個正整數k,要求Pell數列的第k項模上32767是多少。
輸入:第1行是測試資料的組數n,後面跟著n行輸入。每組測試資料佔1行,包括一個正整數k (1 ≤ k < 1000000)。
輸出:n行,每行輸出對應一個輸入。輸出應是一個非負整數。
樣例輸入
2
1
8
樣例輸出
1
408
練習2:3089_爬樓梯
描述:樹老師爬樓梯,他可以每次走1級或者2級,輸入樓梯的級數,求不同的走法數。
例如:樓梯一共有3級,他可以每次都走一級,或者第一次走一級,第二次走兩級也可以第一次走兩級,第二次走一級,一共3種方法。
輸入:輸入包含若干行,每行包含一個正整數N,代表樓梯級數,1<= N <= 30
輸出:不同的走法數,每一行輸入對應一行輸出
樣例輸入
5
8
10
樣例輸出
8
34
89
練習3:2046_骨牌鋪方格
描述:在2×n的一個長方形方格中,用一個1× 2的骨牌鋪滿方格,輸入n ,輸出鋪放方案的總數。例如n=3時,為2× 3方格,骨牌的鋪放方案有三種,如下圖:
輸入:輸入資料由多行組成,每行包含一個整數n,表示該測試例項的長方形方格的規格是2×n(0<n<=50)。
輸出:對於每個測試例項,請輸出鋪放方案的總數,每個例項的輸出佔一行。
樣例輸入
1
3
2
樣例輸出
1
3
2
練習4:2718_移動路線
描述:桌子上有一個m行n列的方格矩陣,將每個方格用座標表示,行座標從下到上依次遞增,列座標從左至右依次遞增,左下角方格的座標為(1,1),則右上角方格的座標為(m,n)。
小明是個調皮的孩子,一天他捉來一隻螞蟻,不小心把螞蟻的右腳弄傷了,於是螞蟻只能向上或向右移動。小明把這隻螞蟻放在左下角的方格中,螞蟻從左下角的方格中移動到右上角的方格中,每步移動一個方格。螞蟻始終在方格矩陣內移動,請計算出不同的移動路線的數目。
對於1行1列的方格矩陣,螞蟻原地移動,移動路線數為1;對於1行2列(或2行1列)的方格矩陣,螞蟻只需一次向右(或向上)移動,移動路線數也為1……對於一個2行3列的方格矩陣,如下圖所示:
-------------------
|(2,1)|(2,2)|(2,3)|
-------------------
|(1,1)|(1,2)|(1,3)|
-------------------
螞蟻共有3種移動路線:
路線1:(1,1) → (1,2) → (1,3) → (2,3)
路線2:(1,1) → (1,2) → (2,2) → (2,3)
路線3:(1,1) → (2,1) → (2,2) → (2,3)
輸入
輸入只有一行,包括兩個整數m和n(0<m+n<=20),代表方格矩陣的行數和列數,m、n之間用空格隔開
輸出
輸出只有一行,為不同的移動路線的數目。
樣例輸入
2 3
樣例輸出
3
提示:移動路線類似爬樓梯問題,但不是一維路徑,而是二維路徑,可以使用二維陣列來記錄到各個位置的路線數量。熟練掌握基本的動態規劃演算法後,可以考慮實現降維優化。
pan � f)=Np��ج → (1,2) → (2,2) → (2,3)路線3:(1,1) → (2,1) → (2,2) → (2,3)
輸入
輸入只有一行,包括兩個整數m和n(0<m+n<=20),代表方格矩陣的行數和列數,m、n之間用空格隔開
輸出
輸出只有一行,為不同的移動路線的數目。
樣例輸入
2 3
樣例輸出
3
提示:移動路線類似爬樓梯問題,但不是一維路徑,而是二維路徑,可以使用二維陣列來記錄到各個位置的路線數量。熟練掌握基本的動態規劃演算法後,可以考慮實現降維優化。