算法 —— 递推

目录

递推

数楼梯

斐波那契数列

一维数组递推

P1002 过河卒

二维数组递推 

P1044 栈

卡特兰数


递推

将一个很大的任务分解成规模小一些的子任务,子任务分成更小的子任务,直到遇到初始条件,最后整理归纳解决大任务的思想就是递推与递归思想,不过这两者还是有一些区别:

  • 递归:从上到下 从未知到已知解决问题
  • 递推:从下到上 从已知到未知解决问题

数楼梯


斐波那契数列

此题很像斐波那契数列的解决方式,斐波那契数列是指这样一个数列:1,1,2,3,5,8,13,21,34,55,89……这个数列从第3项开始 ,每一项都等于前两项之和。我们先看斐波那契在百度百科上的定义:

 一开始尝试用递归函数进行尝试,但是很不幸超时了:

int count_ways(int x)
{
	if (x == 1)
		return 1;
	else if (x == 2)
		return 2;
	else
		return count_ways(x - 1) + count_ways(x - 2);
}

一维数组递推

之后修改了数据类型以及计算方式,打算尝试利用递推算法:

#include<bits/stdc++.h>
using namespace std;

long long stairs[5001] = { 0 };

int main()
{
	int n; cin >> n;
	stairs[1] = 1, stairs[2] = 2;
	for (int i = 3; i <= n; i++)
		stairs[i] = stairs[i - 1] + stairs[i - 2];
	cout << stairs[n] << endl;
	return 0;
}

虽然这次没有超时,但是long long还是不够存放最大数据,很显然要利用高精度加递推解决:

轻松解决,大家对高精度感兴趣的可以看我的这篇博客:算法 —— 高精度(模拟)

接着说一下,为什么这个动态转移方程可行:

 stairs[i] = stairs[i - 1]+stairs[i - 2]

由此我们得出规律,方法数为前2次与前1次之和。 


P1002 过河卒

由于士卒只能向下或者向右走,从题目中可以得出此动态转移方程 :

f[i][j] = f[i-1][j] + f[i][j-1]

以样例6 x 6的棋盘为例,作出以下图像:

 可以看到我们把棋盘扩大了两行两列,避免出现越界访问的情况,此处越界访问共有两处:

  1. 马的坐标如果在边界,他可跳跃的点出现越界访问
  2. 动态转移方程的左、上坐标也会出现越界访问

二维数组递推 

 代码如下所示,这里用到了模拟与递推算法:

#include<bits/stdc++.h>
using namespace std;

const int fx[] = { 0, -2, -1, 1, 2, 2, 1, -1, -2 };
const int fy[] = { 0, 1, 2, 2, 1, -1, -2, -2, -1 };
//马可以走到的位置

int bx, by, hx, hy;
long long f[40][40];   //只有路的棋盘
bool s[40][40]; //只有马的棋盘
int main() {
    cin >> bx >> by >> hx >> hy;
    bx += 2; by += 2; hx += 2; hy += 2;
    //坐标+2以防越界
    f[2][1] = 1;//初始化
    for (int i = 0; i <= 8; i++) //标记不能过的点
        s[hx + fx[i]][hy + fy[i]] = 1;
    for (int i = 2; i <= bx; i++) {
        for (int j = 2; j <= by; j++) {
            if (s[i][j]) continue; // 如果被马拦住就直接跳过

            f[i][j] = f[i - 1][j] + f[i][j - 1];
        }
    }
    cout << f[bx][by] << endl;
    return 0;
}

为什么这样可行?由于士卒棋只能向下或者向右走,所以从一个对角到另外一个对角只存在两种可能情况,如下图所示:

 由图可知,动态转移方程可以表述此过程,因此方法可行。


P1044 栈

 在解决这道题之前先了解一个新概念:卡特兰数


卡特兰数

卡特兰数(英语:Catalan number),又称卡塔兰数、明安图数,是组合数学中一种常出现于各种计数问题中的数列。以比利时数学家欧仁·查理·卡特兰的名字命名。1730年,清代蒙古族数学家明安图在对三角函数幂级数的推导过程中首次发现,1774年被发表在《割圜密率捷法》。百度百科是这样定义卡特兰数的:

我们看一下卡特兰数的概述图:

如果一个点从坐标A(0,0)开始移动到坐标B(n,n),只允许向上或向右,并且存在约束条件 :路径只能在y = x以下,那么我们可以看到,如果要移动到B点,总路程为2n,左图绿色路径为刚好越过约束条件的情况,我们可以看到,绿色非法路径的突出点与橙色路径都不约而同的经过右图蓝色直线,意味着路径只要碰到蓝色直线即变成非法 

我们利用数学方法对这个图形进行分析:

 再次对非法路径进行分析可以得出以下结论:

合法路径方程为:C_{2n}^{ n} - C_{2n}^{n+1} = \frac{C_{2n}^{ n}}{n+1}此式得证。

 这时候将理论运用到实际,我们把右移看作入栈,上移看作出栈,同上出栈数始终比入栈大1才为非法,很显然这是一个卡特兰数问题,注意卡特兰数后面数据过大,long long存不下,很显然还需要结合高精度模拟。(此题给了限定范围最大为卡特兰数第18项即129644790)

#include <iostream>
using namespace std;

long long count_C(int n)
{
	long long tmp = 1; int d = n;
	for (int i = 2 * n; i > n; i--)
	{
		if (i % d == 0 && d > 1)
		{
			tmp *= (i / d); //尽可能缩小乘数
			d--;
		}
		else
			tmp *= i;
	}
	while (d > 1)
		tmp /= d--;
	return tmp;
}

int main()
{
	int n; cin >> n;
	long long ret = count_C(n);
	cout << ret / (n + 1) << endl;
	return 0;
}

感兴趣的小可爱们可以试着加上高精度解决n更大的情况。

如需科普请看这篇博客:「算法入门笔记」卡特兰数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值