1. 程式人生 > 實用技巧 >「基礎演算法」第1章 遞推演算法課堂過關

「基礎演算法」第1章 遞推演算法課堂過關

目錄

「基礎演算法」第1章 遞推演算法課堂過關

A. 【例題1】錯排問題

題意

求多少個n個數的排列A,滿足對於任意的\(i (1\leq i \leq n) A_i \neq i\),輸入n,輸出一個整數,表示答案

思路

\(f(n)\)表示n個數的合法方案排列個數

  1. 考慮第x個元素,把他放到k位置上,一共有 n-1种放法
  2. 考慮第k個元素,共兩種可能:
    1. 把k放在位置n,則對於除k,n以外的其他元素,錯排即可,共\(f(n-2)\)
      種方案
    2. 不把k放在位置n,則對於n-1個元素的錯排,有\(f(n-1)\)種方法

故,有遞推式:

\[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)\)

),然後不對B塔進行操作,將剩下n-j個盤子轉移到D塔,移動步數為\(d(n-j)\),最後將B塔的j個盤子轉移到D塔,移動步數為\(f(j)\),我們直接列舉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\)時:

  1. k份中至少有一份為“1”:方案數為\(f(n-1,k-1)\)
  2. 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. 將1*2的矩形豎著拼到2*(n-1)的矩形上,方案數為f(x-1)
  2. 將兩個1*2的矩形橫著拼到2*(n-2)的矩形上,方案數為f(x-2)
  3. 將一個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;
}