[WHK][数学] 探究性学习之阶梯问题和错排问题

貌似已经有5个月没有写博客了呢,自从退役后就没有再来过,清明假期数学老师布置的探究性学习让我萌生了写博客的想法,那就写一写,正好带到班里装一装。。。

本文全部由作者自己创作,没有借助任何AI工具 (这句话是数学老师让加的)

阶梯问题

有 $ 10 $ 个阶梯,每次可以选择走一个或两个,问走完的方案数;

这里我们用到一种算法,或者叫一种思维,是DP,即Dynamic Planning,中文名叫动态规划

我们以本题为例,理清其最简要的算法思路;

  1. 定义状态

我们把题目中的 $ 10 $ 看做变量,设 $ f_i $ 表示一共走 $ i $ 个阶梯时的方案数,这里的 $ f $ 数组存储的是状态, $ f_i $ 表示在 $ i $ 这个条件下的状态;

  1. 设计状态转移方程

那么我们有状态转移方程(其实就是一个递推关系):

\[f_i = f_{i - 1} + f_{i - 2} \]

这里也很好理解,就是考虑最后一步走一步或两步,这里是分类加法原理,所以要求和;

  1. 初始化

显然有 $ f_1 = 1, f_2 = 2 $;

那么我们就可以从 $ n = 3 $ 开始,递推解决这个问题;

回顾算法思路,其实DP的组成就是这三步,这样我们就可以在线性时间复杂度内( $ \Theta(n) $ ,即 $ n $ 次运算)解决这个问题;

那么对于这个题,不难发现,这就是斐波那契数列去除了第一项,那么我们就可以用其通项公式在 $ \Theta(1) $ (即一次运算)内快速解决这个问题;

所以本题的答案即为 $ Fib $ 的第 $ 11 $ 项,即 $ 89 $;

验证正确性

我们利用信息技术来验证;

下面给出一个程序的源代码,其思路是穷举所有可能,输入所要爬的阶梯数,即可输出答案;

#include <iostream>
#include <cstdio>
using namespace std;
int n;
int cnt;
void dfs(int now) { //深度优先搜索枚举所有可能 
	if (now == n) {
		cnt++;
		return;
	}
	if (now > n) return;
	dfs(now + 1);
	dfs(now + 2);
}
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n;
	if (n < 1) {
		cout << "illegal!";
		return 0;
	}
	dfs(0); //从0开始,因为一开始没有登上任何阶梯 
	cout << cnt;
	return 0;
}

输入 $ 10 $ ,输出 $ 89 $;

这个程序的时间复杂度较高,仅作为验证正确性使用。若想得到一个效率较高的程序,则直接按照DP做即可,可以处理 $ 10^8 $ 以内的数据 当然直接通项公式更快

错排问题

继承上面的思路,设 $ D_n $ 表示 $ n $ 个数错排方案数;

  1. 考虑将1, 2 换位

那么剩下 $ n - 2 $ 个数,将它们错排,有 $ D_{n - 2} $ 中方案。同时,$ 1 $ 可以和 $ 2, 3, 4, ... $ 换位,有 $ n - 1 $ 中可能,总共 $ (n - 1)D_{n - 2} $ 种方案;

  1. 考虑与1换位的数不在1上

那么将这个数看成 $ 1 $ 方案数即为 $ D_{n - 1} $,同时 $ 1 $ 有 $ n - 1 $ 中方法,总共 $ (n - 1)D_{n - 1} $;

所以:

\[D_n = (n - 1)(D_{n - 1} + D_{n - 2}) \]

验证

我们同样借助程序验证;

下面程序枚举了 $ n $ 个数的全排,并给出答案;

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int n;
int a[100005];
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n;
	if (n <= 0) {
		cout << "illgeal!";
		return 0;
	}
	for (int i = 1; i <= n; i++) a[i] = i;
	long long cnt = 0;
	do {
		bool vis = false;
		for (int i = 1; i <= n; i++) {
			if (a[i] == i) {
				vis = true;
				break;
			}
		}
		if (!vis) cnt++;
	} while(next_permutation(a + 1, a + 1 + n));
	cout << cnt;
	return 0;
}

输入$ 1 $ 至 $ 5 $,结果都符合预期;

此算法时间复杂度 $ \Theta(n!) $,仅作验证正确性用;

posted @ 2025-04-04 18:22  Peppa_Even_Pig  阅读(48)  评论(0)    收藏  举报