「基础算法」第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;
} 
posted @ 2020-12-19 16:37  追梦人1024  阅读(173)  评论(0编辑  收藏  举报
Live2D