「基礎演算法」第1章 遞推演算法課堂過關
阿新 • • 發佈:2020-12-19
目錄
),然後不對B塔進行操作,將剩下n-j個盤子轉移到D塔,移動步數為\(d(n-j)\),最後將B塔的j個盤子轉移到D塔,移動步數為\(f(j)\),我們直接列舉j,取最小值即可
「基礎演算法」第1章 遞推演算法課堂過關
A. 【例題1】錯排問題
題意
求多少個n個數的排列A,滿足對於任意的\(i (1\leq i \leq n) A_i \neq i\),輸入n,輸出一個整數,表示答案
思路
設\(f(n)\)表示n個數的合法方案排列個數
- 考慮第x個元素,把他放到k位置上,一共有 n-1种放法
- 考慮第k個元素,共兩種可能:
- 把k放在位置n,則對於除k,n以外的其他元素,錯排即可,共\(f(n-2)\)
- 不把k放在位置n,則對於n-1個元素的錯排,有\(f(n-1)\)種方法
- 把k放在位置n,則對於除k,n以外的其他元素,錯排即可,共\(f(n-2)\)
故,有遞推式:
\[f(x)= \left\{ \begin{aligned} (n-1)\cdot (f(x - 1) + f(x - 2)) \quad\ \ (x\geq3)\\ 1\qquad\qquad\qquad\qquad\qquad\qquad\qquad(x=1)\\ 2\qquad\qquad\qquad\qquad\qquad\qquad\qquad(x=2) \end{aligned}\right. \]程式碼
#include <iostream> #include <cstdio> #define int long long using namespace std; int n; int ans; int f(int x) {//資料異常水,記憶化都不用 if(x == 1) return 0; if(x == 2) return 1; return (x - 1) * (f(x - 1) + f(x - 2)); } signed main() { cin >> n; cout << f(n); cout << endl; return 0; } /* 0 1 3 12 56 321 2175 17008 150504 1485465 */
B. 【例題2】奇怪漢諾塔
題意
有A、B、C、D四座塔, 給定n個圓盤,一開始全部放在A塔,直接輸出n=1~12時的所有答案,無輸入
具體規則同普通漢諾塔
思路
回顧三座塔的情況:
將A塔的n-1個圓盤移至B塔,最後一個圓盤直接移至C塔,再將B塔的所有圓盤移到C塔
有遞推式:
\[d(x)= \left\{ \begin{aligned} &1+2\cdot d(x - 1) \qquad(x\geq2)\\ &1\qquad\qquad\qquad\qquad(x=1) \end{aligned}\right. \]當漢若塔數量為4座時:
考慮將A塔的j個盤子轉移到B塔(移動步數為\(f(j)\)
遞推式:
\[f(x)= \left\{ \begin{aligned} &\min {\{d(x-j)+2\cdot f(j)}\} \qquad(x\geq2,j\in[0,x])\\ &1\qquad\qquad\qquad\qquad\qquad\qquad\ (x=1) \end{aligned}\right. \]程式碼
#include <iostream>
#include <cstdio>
#define min_(_ , __) (_ < __ ? _ : __)
using namespace std;
int d[20] , f[20];
int main() {
d[1] = 1;
for(int i = 1 ; i <= 12 ; i++)
d[i] = 2 * d[i - 1] + 1;
f[1] = 1;
for(int i = 2 ; i <= 12 ; i++) {
f[i] = (1 << 29);
for(int j = 0 ; j <= i ; j++)
f[i] = min_(f[i] , 2 * f[j] + d[i - j]);
}
for(int i = 1 ; i <= 12 ; i++)
cout << f[i] << endl;
return 0;
}
/*答案
1
3
5
9
13
17
25
33
41
49
65
81
*/
C. 【例題3】數的劃分
題意
將整數n分成k份,每份不為空,求不同的劃分方案數(數字相同而順序不同的兩個方案視為相同的方案)
例如:下面三種分法被認為是相同的:
1,1,5; 1,5,1; 1,1,5.
思路
設$ f(n,k)$表示將整數n分成k份的不重複方案數
當\(n>k\)時:
- k份中至少有一份為“1”:方案數為\(f(n-1,k-1)\)
- k份中沒有一份為“1” :方案數為\(f(n-k,k)\)(在n-k被分成k份的基礎上,每一份都加上“1”)
故有遞推式:
\[f(n,k)= \left\{ \begin{aligned} &f(n-1,k-1)+f(i-k,k) \qquad\ \ \ (x>k)\\ &1\qquad\qquad\qquad\qquad\qquad\qquad\qquad(n=k)\\ &0\qquad\qquad\qquad\qquad\qquad\qquad\qquad(n<k) \end{aligned}\right. \]程式碼
#include <iostream>
#include <cstdio>
using namespace std;
int n , k;
int f[1010][1010];
int main() {
cin >> n >> k;
for(int i = 1 ; i <= n ; i++)
for(int j = 1 ; j <= k ; j++) {
if(i == j) f[i][j] = 1;
else if(i < j) f[i][j] = 0;
else f[i][j] = f[i - 1][j - 1] + f[i - j][j];'
}
cout << f[n][k];
return 0;
}
D. 【例題4】傳球遊戲
題目
思路
設\(f(i , j)\) 表示經過j次傳球后,球落在第i個同學手中的方案數(說明一下,我程式碼裡面把遊戲剛開始,球在小蠻手上的情況當做第1次傳球,故有一丟丟不一樣)
很顯然,有:
\[f(i,j)=f(i-1,j-1)+f(i+1,j-1) \]其中,根據題目“n 個同學站成一個圓圈”的背景,“i+1” 、"i-1"均應該在\([1,n]\)範圍內,越界自行處理
程式碼
#include <iostream>
#include <cstdio>
#define ll long long
using namespace std;
int n , m;
ll f[3010][3];
int main() {
cin >> n >> m;
++m;
f[1][1] = 1;
for(int i = 2 ; i < m ; i++) {
for(int j = 1 ; j <= n ; j++){
f[j][i & 1] = 0;
f[j][i & 1] = f[j == 1 ? n : j - 1][(i - 1) & 1] + f[j == n ? 1 : j + 1][(i - 1) & 1];
}
}
cout << f[n][(m - 1) & 1] + f[2][(m - 1) & 1] << endl;
return 0;
}
E. 【例題5】平鋪方案
題目
思路
想出並寫出演算法10min,寫出高精度40min
設\(f(x)\)表示鋪成2*x的矩形的方案數
顯然\(f(1)=1,f(2)=3\)
對於其他情況,我們考慮:
- 將1*2的矩形豎著拼到2*(n-1)的矩形上,方案數為f(x-1)
- 將兩個1*2的矩形橫著拼到2*(n-2)的矩形上,方案數為f(x-2)
- 將一個2*2的矩形橫著拼到2*(n-2)的矩形上,方案數為f(x-2)
故遞推式:
\[f(x)= \left\{ \begin{aligned} &2\cdot f(x - 2) + f(x - 1) \quad(x\geq3)\\ &1\qquad\qquad\qquad\qquad\qquad(x=1)\\ &3\qquad\qquad\qquad\qquad\qquad(x=2) \end{aligned}\right. \]最後,看樣例都知道要高精度
程式碼
#include <iostream>
#include <cstdio>
#include <cstring>
#define ll long long
using namespace std;
#define m 1010
struct bignum {
int a[m];
int siz;
void init() {
memset(a , 0 , sizeof(a));
this->siz = 0;
}
void print() {
// cout << siz << endl;
for(int i = 0 ; i < siz ; i++)
putchar(a[siz - i - 1] + '0');
putchar('\n');
}
bignum operator + (const bignum &b) const{
bignum ans;
ans.init();
int g = 0;
ans.siz = (siz > b.siz ? siz : b.siz) - 1;
for(int i = 0 ; i <= ans.siz || g != 0 ; i++) {
if(i > ans.siz)
ans.siz = i;
ans.a[i] = g + a[i] + b.a[i];
g = ans.a[i] / 10;
ans.a[i] %= 10;
}
++ans.siz;
if(ans.a[ans.siz - 1] == 0)--ans.siz;
return ans;
}
};
bignum f[1010];
int main() {
int n;
int maxn = 3;
f[1].siz = 1 , f[1].a[0] = 1;
f[2].siz = 1 , f[2].a[0] = 3;
while(cin >> n) {
for(; maxn <= n ; maxn++)
f[maxn] = f[maxn - 1] + f[maxn - 2] + f[maxn - 2];
f[n].print();
if(n > maxn) maxn = n;
}
return 0;
}