遞推算法
什么是遞推
遞推就是一種若干步可重復運算來描述復雜問題的方法,遞推是一種重要的數學方法,也是編程編程解決問題的常用方法。————小到大,已知推出未知
遞推有什么特征
特點:一個問題求解需要一系列計算,這一系列的計算的步驟中存在着關聯關系;在計算時,如果可以找到前后過程之間的數量關系(即遞推式),那么就可以從已知條件推導出最終結果!
如等差數列就可以用遞推公式求出:
1 3 5 7 9
由小到大,通過已知及其數量關系找出遞推式推出未知:
A(n) = A(n - 1) + 2
求斐波那契數列的第 n 項
請使用遞歸的方式求斐波那契數列的第 n 項。
斐波那契數列:1,1,2,3,5…,這個數列從第 3 項開始,每一項都等於前兩項之和
輸入格式
共一行,包含整數 n。
輸出格式
共一行,包含一個整數,表示斐波那契數列的第 nn 項。
數據范圍
1≤n≤30
輸入樣例:
4輸出樣例:
3
遞歸做法:
當n越來越大時,執行時間會越來越長,直到超時(時間復雜度:指數級別)
因為隨着n的增大,重復解的項就會越來越多(但又不得不計算),這棵樹就會越來越大,耗時也增大!

【參考代碼】
#include<iostream>
using namespace std;
int f(int n)
{
if(n == 0 || n == 1) return 1;
return f(n - 1) + f(n - 2);
}
int main()
{
int n;
cin>>n;
cout<<f(n);
return 0;
}
改進方法:使用數組和遞推的方法提高遞歸的效率
上述時間超限,那我們可以通過減少不必要的計算來降低復雜度,即減少重復的計算,那我們可以通過遞推的方式來優化——可以用一個數組來記錄重復計算的結果,當下次再用時,直接使用即可!

【參考代碼】
#include<iostream>
using namespace std;
const int N = 1e5 + 10;
typedef long long LL;
LL a[N], n;
int f(LL n)
{
// 已知量
a[1] = 1;
a[2] = 2;
// 小到大,已知到未知,有關系得出遞推公式
for(int i = 3; i <= n; i ++)
{
a[i] = a[i - 1] + a[i - 2];
}
return a[n];
}
int main()
{
cin>>n;
cout<<f(n);
return 0;
}
改進方法:利用變量迭代的方法提高遞歸的效率
采用兩個變量x和y分別記錄 n - 1項的數和n- 2項的數
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
LL x, y, n, z;
int main()
{
cin >> n;
x = 1;
y = 1;
// 1 1 2 3 5 1 1 2 3 5
// x y z x y z
for(int i = 3; i <= n; i ++) // z 為前兩項的和故可以使用迭代 —— 減少重復的計算
{
z = x + y;
x = y;
y = z;
}
if(n < 3) cout << 1 ;
else cout << z;
}
猴子吃桃問題

思路:第二天吃掉剩下的一半多一個,第十天還剩下1個。有已知到未知,可以從后往前遞推!
10 :1
9:4 (2 * (1 + 1))
8:10 (2 *(4 + 1))
【參考代碼】
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
LL x, y, n, z;
int main()
{
int ans = 1;
for(int i = 9; i >= 1; i --)
{
ans = 2 * (ans + 1);
}
cout << ans;
}
Pell數列
題目描述
有一種數列,它的前10項的值分別為:1 2 5 12 29 70 169 408 985 2378,這個數列被稱為Pell數列,請問該數列的第n項的值是多少?(n<=1000)
輸入
一個整數n
輸出
第n項的值
樣例
102378
思路:
不難看出從第3項開始,每一項的數等於前一項數的兩倍與前前一項的和。
但是當n和大時,結果就很大很大超出了int 甚至long long的范圍而導致數據溢出,因此還要用到高精度算法!
求和時可以用迭代求和!
1 2 5 12 ....
遞推公式:An = 2*A(n - 1 ) + A(n - 2)
【參考代碼】
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long LL;
LL n;
string add(string a, string b)
{
// 記錄答案
string res;
vector<int>A, B, C;
for(int i = a.size() - 1; i >= 0; i--) A.push_back(a[i] - '0'); //A = [6,5,4,3,2,1];
for(int i = b.size() - 1; i >= 0; i--) B.push_back(b[i] - '0');
// 大的在上,小的在下
if(A.size() < B.size()) return add(b,a);
int t = 0; // 進位
for(int i = 0; i < A.size(); i ++)
{
t += A[i];
if(i < B.size()) t += B[i];
C.push_back(t % 10);// 記錄答案
t /= 10; // 進位
}
// 判斷最后一位是否需要進位!
if(t) C.push_back(1);
for(int i = C.size() - 1; i >= 0; i --) res += (C[i] + '0');
return res;
}
string mul(string a)
{
string res;
vector<int>A, C;
for(int i = a.size() - 1; i >= 0; i--) A.push_back(a[i] - '0');
int t = 0; //用t來記錄乘積結果以及進位
for(int i = 0; i < A.size(); i ++)
{
t += A[i] *2;
C.push_back(t % 10);
t /= 10;
}
// 判斷最后一位是否要進位
while(t)
{
C.push_back(t % 10);
t /= 10;
}
// 去除前導0
while(C.size() > 1 && C.back() == 0) C.pop_back();
for(int i = C.size() - 1; i >= 0; i --) res += (C[i] + '0');
return res;
}
int main()
{
// An = 2*A(n - 1 ) + A(n - 2)
string x, y, z;
cin >> n;
x = "1";
y = "2";
if(n == 1) cout << x;
else if(n == 2) cout << y;
else // 從第三項開始遞推(迭代求和)
{
for(int i = 3; i <= n; i ++)
{
//1 2 5 12 1 2 5 12
//x y z x y z
z = add(mul(y), x);
// 修改x,y的值,逐步往后推導
x = y;
y = z;
}
cout << z;
}
return 0;
}
數木塊

思路:
第1層:1個
第2層:3個
第3層:6個
第4層:10個
.....
第i層的木塊 = i + 上一層的木塊數量
【參考代碼】
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
LL x, y, n, z;
int a[110];
int main()
{
cin >> n;
a[1] = 1;
int sum = 0;
for(int i = 2; i <= n; i ++) // 從第2層開始
{
a[i] = i + a[i - 1];
sum += a[i];
}
if(n == 1) cout << 1;
// 加上第一層的木塊
else cout << sum + 1;
}
數塔問題
題目描述
有如下所示的數塔,要求從底層走到頂層,若每一步只能走到相鄰的結點,則經過的結點的數字之和最大是多少?

輸入
輸入數據首先包括一個整數整數N(1 <= N <= 100),表示數塔的高度,接下來用N行數字表示數塔,其中第i行有個i個整數,且所有的整數均在區間[0,99]內。
輸出
從底層走到頂層經過的數字的最大和是多少?
樣例
5 7 3 8 8 1 0 2 7 4 4 4 5 2 6 530
思路:
從底層走到頂層經過的數字的最大和是多少?
我們可開一個二維數組來記錄走到某一個位置時數字的最大和是多少,由於數都是已知的,由已知到未知,由小到大,我們可以從底部遞推到頂部,最后一行的最大值就是自己本身,從n-1行開始,改行上每一個位置的最大和由正下方和左下方的數來決定,要使得和最大,那么就從正下方和左下方的數中選擇較大的那一個!層層往上遞推,最終頂部位置的和就最大。
【參考代碼】
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
LL x, y, n, z;
int a[110][110];
int main()
{
cin >> n;
// 讀入數塔
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= i; j ++){
cin >> a[i][j];
}
}
// 從第 n- 1行開始 往上遞推!
for(int i = n - 1; i >= 1; i --){
for(int j = 1; j <= n - 1; j ++){
a[i][j] = a[i][j] + max(a[i + 1][j], a[i + 1][j + 1]);
}
}
cout << a[1][1];
}
過河卒
題目描述
A 點有一個過河卒,需要走到目標 B 點。卒行走規則:可以向下、或者向右。同時在棋盤上的任一點有一個對方的馬(如下圖的C點),該馬所在的點和所有跳躍一步可達的點稱為對方馬的控制點。例如下圖 C 點可以控制 9 個點(圖中的P1,P2 … P8 和 C)。卒不能通過對方馬的控制點。 棋盤用坐標表示,現給定A 點位置為(0,0)B 點位置為(n,m)(n,m 為不超過 20 的整數),馬的位置為C(X,Y)(約定: C點與A點不重疊,與B點也不重疊)。要求你計算出卒從 A 點能夠到達 B 點的路徑的條數。
輸入
B點的坐標(n,m)以及對方馬的坐標(X,Y)(馬的坐標一定在棋盤范圍內,但要注意,可能落在邊界的軸上)
輸出
樣例
輸入
6 6 3 2輸出
17
思路:
注:卒行走規則:可以向下、或者向右
當沒有馬時由起點到終點由幾種方案呢?

那如果存在障礙物呢?也就是馬兒以及馬兒所控制的范圍!
- 我們可以將棋盤初始化設計為1
- 標記馬和馬所控制的點為0
- a[0] [0]不用計算,除此以為
- i = 0,第一行,除了控制點,a[i] [j]等左側的值
- j = 0,第一行,除了控制點,a[i] [j]等正上方的值
- 其余的點,除了控制點和原點以外,a[i] [j] = 左側 + 正上方
【參考代碼】
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
LL n, m, x, y;
int a[110][110];
int main()
{
//讀入終點坐標 和 馬的坐標
cin >> n >> m >> x >> y;
// 初始化棋盤
for(int i = 0; i <= n; i ++){
for(int j = 0; j <= m; j ++){
a[i][j] = 1;
}
}
// 標記馬的控制點,馬及其八個方向!
a[x][y] = 0;
if(x - 2 >= 0 && y + 1 <= m) a[x - 2][y + 1] = 0;
if(x - 1 >= 0 && y + 2 <= m) a[x - 1][y + 2] = 0;
if(x + 1 <= n && y + 2 <= m) a[x + 1][y + 2] = 0;
if(x + 2 <= n && y + 1 <= m) a[x + 2][y + 1] = 0;
if(x + 2 <= n && y - 1 >= 0) a[x + 2][y - 1] = 0;
if(x + 1 <= n && y - 2 >= 0) a[x + 1][y - 2] = 0;
if(x - 1 >= 0 && y - 2 >= 0) a[x - 1][y - 2] = 0;
if(x - 2 >= 0 && y - 1 >= 0) a[x - 2][y - 1] = 0;
// 遞推計算
for(int i = 0; i <= n; i ++){
for(int j = 0; j <= m; j ++){
if(i == 0 && j == 0) continue; // a[0][0] 不用算
if(a[i][j] == 0) continue;// 馬控制的點不需要算
if(i == 0) a[i][j] = a[i][j - 1];// 第一行
else if(j == 0) a[i][j] = a[i - 1][j];// 第一列
else
{
a[i][j] = a[i][j - 1] + a[i - 1][j];
}
}
}
cout << a[n][m];
}
數花生問題
(動態規划)
題目描述
Hello Kitty 想摘點花生送給她喜歡的米老鼠。她來到一片有網格狀道路的矩形花生地(如下圖),從西北角進去,東南角出來。地里每個道路的交叉點上都有種着一株花生苗,上面有若干顆花生,經過一株花生苗就能摘走該它上面所有的花生。Hello Kitty只能向東或向南走,不能向西或向北走。問Hello Kitty 最多能夠摘到多少顆花生。
如輸入:
2 2
1 1
3 4
代表有2行,每行有2株花生,那么摘能摘到的最多的花生就是:1->3->4,總和為8顆花生。輸入
第一行是兩個整數m和n(m<=100,n<=100),代表了花生地里有m行,每行有n列的花生!
后面m行,每行有n個整數代表了每行中,每株花生的數量!
輸出
輸出是一個整數,代表了最多能摘到的花生的總數
樣例
輸入
2 2 1 1 3 4輸出
8
【參考代碼】
a[i] [j]:表示走到(i,j)位置所獲得的最多花生!
遞推公式: a[i][j] = a[i][j] + max(a[i][j - 1], a[i - 1][j])
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long LL;
const int N = 110;
LL m, n;
int a[N][N];
int main()
{
cin >> m >> n;
memset(a, 0, sizeof a); // 初始化數組為0
// 讀入地圖
for(int i = 1; i <= m; i ++){
for(int j = 1; j <= n; j ++){
cin >> a[i][j];
}
}
// a[i][j]:表示走到(i,j)位置所獲得的最多花生
for(int i = 1; i <= m; i ++){
for(int j = 1; j <= n; j ++){
a[i][j] = a[i][j] + max(a[i][j - 1], a[i - 1][j]);
}
}
cout << a[m][n];
return 0;
}
蜜蜂路線
題目描述
一只蜜蜂在下圖所示的數字蜂房上爬動,已知它只能從標號小的蜂房爬到標號大的相鄰蜂房,現在問你:蜜蜂從蜂房M開始爬到蜂房N,1=<M<N<=100,有多少種爬行路線?
輸入
輸入M,N的值。 (1=<m<n<=100)
輸出
爬行有多少種路線。
樣例
輸入
1 14輸出
377
思路:
它只能從標號小的蜂房爬到標號大的相鄰蜂房
我們想從1 ——> n 分析有多少步數
f(n)表示走1到n的所有步數
f(1) = 1
f(2) = 1
f(3) = 2
f(4) = 3 ........
我們發現從第3項開始,每一項的結果都為前兩項的和!,每項的結果都必定有前兩項決定!—— 也就是斐波那契數列
那么從 N —— M呢?
例如:7 —— > 10有幾種 —— 要走到10,必然要先到8或9
7 —— > 7:1 (自己到自己也是1種)
7 ——> 8:1
7 ——> 9:2
7 ——> 10:3
我們發現更換了起始點絲毫不影響,還是個斐波那契數列,值得注意的是當n很大時,就會溢出,因此要用高精度算法!
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long LL;
const int N = 110;
LL m, n;
int a[N];
int main()
{
cin >> m >> n;
a[m] = 1, a[m + 1] = 1;
for(int i = m + 2; i <= n; i ++)
{
a[i] = a[i - 1] + a[i - 2];
}
cout << a[n];
return 0;
}
當數據很龐大時,求和的結果就會溢出————高精度算法來解決這一求和過程
【參考代碼】
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long LL;
const int N = 110;
LL m, n;
int a[N];
string add(string a, string b)
{
if(a.size() < b.size()) return add(b, a); //保證大的在上
string res;
vector<int> A, B, C;
for(int i = a.size() - 1; i >= 0; i --) A.push_back(a[i] - '0');
for(int i = b.size() - 1; i >= 0; i --) B.push_back(b[i] - '0');
int t = 0;
for(int i = 0; i < A.size(); i ++)
{
t += A[i];
if(i < B.size()) t += B[i];
C.push_back(t % 10);
t /= 10;
}
if(t) C.push_back(1);
for(int i = C.size() - 1; i >= 0; i --) res += (C[i] + '0');
return res;
}
int main()
{
cin >> m >> n;
string x = "1";
string y = "1";
string z;
for(int i = m + 2; i <= n; i ++) // 迭代求和
{
// 1 1 2 3 5 1 1 2 3 5
// x y z x y z
z = add(x, y);
x = y;
y = z;
}
cout << z;
return 0;
}
骨牌問題
題目描述
有1×n(n<=50)的一個長方形,用一個1×1、1×2和1×3的骨牌鋪滿方格,請問有多少種鋪法?
例如當n=3時為1×3的方格。此時用1×1、1×2和1×3的骨牌鋪滿方格,共有四種鋪法。如下圖:
輸入
一個整數n(n<=50)
輸出
骨牌的鋪法
樣例
34
n為4的時候,方法數F4的分析如下:
第一塊放11,有一種方法;剩余3塊方法數是F3=4種。根據乘法原理,該種情況的方法數是14=4。
第一塊放12,有一種方法;剩余2塊方法數是F2=2種。根據乘法原理,該種情況的方法數是12=2.
第一塊放13,有一種方法;剩余1塊方法數是F1=1種。該種情況方法數是11=1.
根據分類原理,F4=4+2+1=7種。
……
故n>=4的時候,Fn=Fn-1+Fn-2+Fn-3
【參考代碼】
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long LL;
const int N = 110;
LL m, n;
int a[N];
int main()
{
cin >> n;
a[1] = 1;
a[2] = 2;
a[3] = 4;
// 從第4項開始每一項都是前一項的2倍數
for(int i = 4; i <= n; i ++)
{
a[i] = a[i - 1] + a[i - 2] + a[i -3];
}
cout << a[n];
return 0;
}
小X放骨牌
小X喜歡下棋。
這天,小X對着一個長為N寬為M 的矩形棋盤發呆,突然想到棋盤上不僅可以放棋子, 還可以放多米諾骨牌。
每個骨牌都是一個長為2寬為1的矩形,當然可以任意旋轉。小X想知道在骨牌兩兩不重疊的前提下,這個棋盤上最多能放多少個骨牌,希望你幫幫他。輸入
第一行包含用一個空格隔開的兩個整數N,M。
輸出
第一行包含一個整數,表示該棋盤上最多能放的骨牌個數。
樣例
輸入
2 3輸出
3提示
如圖所示,三種顏色分別對應了三個骨牌。
數據范圍
對於30%的數據,N,M≤4。
對於60%的數據,N,M≤1000。
對於 100%的數據,1≤N,M≤40000。
放的牌子都是2個!
可以從小的行列來推出關系

不論是偶數*偶數還是偶數*奇數還是奇數*奇數都是(n * m)/2,最多填的只能是2的倍數!
【參考代碼】
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long LL;
const int N = 110;
LL m, n;
int a[N];
int main()
{
cin >> n >> m;
cout << (n * m) / 2;
return 0;
}
平面分割問題
設有n條封閉曲線畫在平面上,而任何兩條封閉曲線恰好相交於兩點,且任何三條封閉曲線不相交於同一點,問這些封閉曲線把平面分割成的區域個數。

輸入
一個整數n(n<=10000),代表封閉曲線的條數
輸出
n條曲線分割區域的個數
樣例
24
當前平面的個數等於前一個圖形平面的個數加上新增的平面的個數。
前一個平面的個數就是a[n-1]。新增的平面的個數恰好是上一圖橢圓數量的兩倍,也就是2*(n-1)。
所以遞推式就是:a[i]=a[i-1] + 2*(i-1);
【參考代碼】
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
LL m, n;
LL a[N];
int main()
{
cin >> n;
a[1] = 2;
for(int i = 2; i <= n; i ++)
{
a[i] = a[i - 1] + 2 * (i - 1);
}
cout << a[n];
return 0;
}





