递推算法简介

递推算法常用来求解多种可能路径,所有解的个数,一般找出通项公式即可,但对于某些较大数据的题目,处理起来可能存在些问题。运行时间最少的一种办法是找出递推公式之后,用离散数学上的求解特征方程的办法得到通项公式,不过貌似不太常用。

**

1.上学路线问题

**
题目描述
小D从家到学校的道路结构是这样的:
在这里插入图片描述
由n条东西走向和m条南北走向的道路构成了一个n*m的网格,每条道路都是单向通行的(只能从北向南,从西向东走)。
已知小D的家在网格的左上角,学校在网格的右下角。
问小D从他的家到学校一共有多少种不同的上学路线。

输入
两个正整数n和m,意义如题目所述;
(n,m <= 1000)

输出
小D上学路线数量,结果对1000000007取余

样例输入
3 4

样例输出
10

解析:
这是应用递推公式的一道简单的题目,学校(右下角的点)只能是通过其相邻的左边和上边的点而来,左边和上边的点又来自它们的左边和上边的点,所以实际上可以递归定义下去,应当注意的是:对于边上的点来说,到达它们只有一种走法。
代码如下:

#include<iostream>
using namespace std;
int n, m;
long long fd[1000][1000] = {0};//记忆化搜索,用来保存到点x,y的路径数目
long long sove(int x,int y)
{
	if (x <= 1 || y <= 1)
		return 1;
	if (fd[x][y] == 0)//不知道到这个点有多少条
	{                                              /*结果太大,对其取模,题目中要求的*/
		fd[x][y] = (sove(x - 1, y) + sove(x, y - 1))% 1000000007;//递归求解,并记忆
		return fd[x][y];
	}
	else//以前递归搜索过,直接返回记忆下的值,不再递归
		return fd[x][y];
}
int main(void)
{
	cin >> n >> m;
	long long ans = sove(n,m);
	cout << ans << endl;
	return 0;
}

2.骨牌铺方格

题目描述
在2×n的一个长方形方格中,用一个1× 2的骨牌铺满方格,输入n ,输出铺放方案的总数.
例如n=3时,为2× 3方格,骨牌的铺放方案有三种,如下图:
在这里插入图片描述
输入
输入数据由多行组成,每行包含一个整数n,表示该测试实例的长方形方格的规格是2×n (0<n<=50)。

输出
对于每个测试实例,请输出铺放方案的总数,每个实例的输出占一行。

样例输入
1
3
2

样例输出
1
3
2

解析:
观察试题:f(1)=1,f(2)=2,f(3)=3
最后一个骨牌的排列方式有两种:
1.竖排,此时n格之前就有f(n-1)种排列方式(不影响之前的排列)
2.横排,此时有f(n-2)种排列方式(因为多占了一行,把n-1占了)
得出:f(n)=f(n-1)+f(n-2) (n>=3)是斐波那契数列

这里推荐一种大整数存储类型—— _int64(亲测是可以保存2的62次方)

代码如下:

#include<iostream>
using namespace std;
int main(void)
{
	_int64 a[52];
	a[0] = 0, a[1] = 1, a[2] = 2;
	for (int i = 3; i < 51; i++)
		a[i] = a[i - 1] + a[i - 2];
	int n;
	while (cin >> n)
		cout << a[n] << endl;
	return 0;
}

3.LELE的RPG难题

题目描述
有排成一行的n个方格,用红(Red)、粉(Pink)、绿(Green)三色涂每个格子,每格涂一色;
要求任何相邻的方格不能同色,且首尾两格也不同色.
求全部的满足要求的涂法.

输入
输入数据包含多个测试实例,每个测试实例占一行,由一个整数n组成,(0<n<=50)。

输出
对于每个测试实例,请输出全部的满足要求的涂法,每个实例的输出占一行。

样例输入
1
2

样例输出
3
6

分析:
可以这样考虑这个递推问题,第n个的颜色与n-1和第1个有关,所以可以考虑n-1和n的关系:n-1和1颜色相同,n-1和1颜色不同
1:相同时,f(n)=2f(n-1),但是,该种情况在n-1排列时不存在(首尾相同),所以应写为f(n)=2f(n-2)
2:不同时,f(n)=f(n-1) (三种颜色有两种不能用了,只可以涂一种了)

参考代码:

#include<iostream>
using namespace std;
long long a[55];
int main(void)
{
	a[0] = 0, a[1] = 3, a[2] = 6;
	for (int i = 3; i < 51; i++)
		a[i] = 2 * a[i - 2] + a[i - 1];
	int n;
	while (cin >> n)
		cout << a[n] << endl;
	return 0;
}

4.脑洞大开

题目描述
有 n 个箱子,颜色分别为 1…n ;
还有 n 个球,颜色也分别为 1…n ;
现在要将每一个球分别放入一个箱子里,并且一个箱子里只能放一个球。
试求有多少种方案满足:每个箱子,和它里面球的颜色,都不一样。

输入
输入数据的第一行是一个整数T,表示测试实例的个数;
然后是T 行数据,每行包含一个整数 n <= 20

输出
对于每个测试实例,请输出发生相应的方案数。

样例输入
3
1
2
3

样例输出
0
1
2

分析:
记n个球的情况为D_n
可以考虑一种通用的情况,第n个球放入了第k个箱子(n肯定不能放入第n个箱子),此时n有n-1种选择,那么对于第k个球,只有两种情况:k放入了第n个箱子,k没有放入第n个箱子(同样的,k也不能放入第k个箱子)。
1.k放入了第n个箱子,对于剩下的n-2个球(n和k就不影响他们了)就有D_n-2种情况
2.k没有放入第n个箱子,此时可以视为有n-1个球的情况,即可能数为D_n-1
所以:D_n=(n-1)*(D_n-1 + D_n-2)
显而易见的:D_1=0 , D_2=1

参考代码:

#include<iostream>
using namespace std;
long long D[22];
int main(void)
{
	D[0] = 0, D[1] = 0, D[2] = 1;
	for (int i = 3; i < 21; i++)
		D[i] = (i - 1) * (D[i - 1] + D[i - 2]);

	int T;
	cin >> T;
	while (T--)
	{
		int n;
		cin >> n;
		cout << D[n] << endl;
	}
	return 0;
}

**

5.一只小蜜蜂

**

题目描述
有一只经过训练的蜜蜂只能爬向右侧相邻的蜂房,不能反向爬行。
请编程计算蜜蜂从蜂房a爬到蜂房b的可能路线数。
其中,蜂房的结构如下所示:

在这里插入图片描述

输入
输入数据的第一行是一个整数T(T <= 100000),表示测试实例的个数;
然后是T 行数据,每行包含两个整数a和b(0<a<b<=100000)。

输出
对于每个测试实例,请输出蜜蜂从蜂房a爬到蜂房b的可能路线数,每个实例的输出占一行。
输出结果对 109+7 取模。

样例输入
2
1 2
3 6

样例输出
1
3

分析:
如果从第一个蜂房开始,那么对于第n个蜂房,到达其所有可能的数记为fn,n只能从n-1或者n-2得到,那么显然fn=f(n-1)+f(n-2) n>2
易知f1=1,f2=1

参考代码:

#include<iostream>
using namespace std;
long long fd[1000050];
int mod = 1e5 + 7;
int main(void)
{
	fd[1] = fd[2] = 1;
	for (int i = 3; i < 1000001; i++)
		fd[i] = (fd[i - 1] + fd[i - 2]) % mod;
	int T;
	cin >> T;
	int a, b;
	while (T--)
	{
		cin >> a >> b;
		cout << fd[b - a + 1] << endl;
	}
	return 0;
}

如果超时的话,可以考虑改成C

6.No Double O

题目描述
开心的小明准备在元旦晚会上给他喜欢的女孩一块巧克力;
他在上面刻下一个长度为 n 的只由"L" “O” “V” “E” 四种字符组成的字符串(可以只有其中一种、两种或三种字符,但绝对不能有其他字符);
小明同时禁止在串中出现"O"相邻的情况,他认为,"OO"看起来就像发怒的眼睛,效果不好。
你能帮小明算一下一共有多少种满足要求的不同的字符串吗?

输入
输入数据包含多个测试实例,每个测试实例占一行,由一个整数n组成,(0<n<=1000)。
由于结果很大,你需要对 1e9+7 取模。

输出
对于每个测试实例,请输出全部的满足要求的涂法,每个实例的输出占一行。

样例输入
1
2

样例输出
4
15

分析:
有一个较为简单的考虑方法,我们把这种字符串分为两类,一种是以O结尾的,另一种是不以O结尾的,所以总数num=is + not
对于n长度的字符串,它的is 和 not 的数量可以如下得出:
n-1长度任意字符结尾的字符串和“LVE”组合即得到n长度的,不以O结尾的字符串
n-1长度的不以O结尾的字符串和 O 组合,即得到n长度的以O结尾的字符串
综上:num[n]=is[n]+not[n]
is[n]=not[n-1]
not[n]=3num[n]=3(is[n-1]+not[n-1])

参考代码:

#include<iostream>
using namespace std;
const int mod = 1e9 + 7;
const int N = 1010;
int main(void)
{
	long long Is[N], Not[N];
	Is[1] = 1, Not[1] = 3;
	for (int i = 2; i < 1001; i++)
	{
		Not[i] = (3 * (Is[i - 1] + Not[i - 1]))%mod;
		Is[i] = (Not[i - 1])%mod;
	}
	int n;
	while (cin >> n)
	{
		long long num = (Is[n] + Not[n]) % mod;
		cout << num << endl;
	}
	return 0;
}

7.考新郎
题目描述
国庆期间,省城HZ刚刚举行了一场盛大的集体婚礼,为了使婚礼进行的丰富一些,司仪临时想出了有一个有意思的节目,叫做"考新郎";
具体的操作是这样的:
首先,给每位新娘打扮得几乎一模一样,并盖上大大的红盖头随机坐成一排;
然后,让各位新郎寻找自己的新娘.每人只准找一个,并且不允许多人找一个.
最后,揭开盖头,如果找错了对象就要当众跪搓衣板…
看来做新郎也不是容易的事情…
假设一共有N对新婚夫妇,其中有M个新郎找错了新娘,求发生这种情况一共有多少种可能.

输入
输入数据的第一行是一个整数C (C <= 1000000),表示测试实例的个数;
然后是C行数据,每行包含两个整数N和M(1<M<=N<=20)。
由于结果可能很大,需要结果对 1e9+7 取模;

输出
对于每个测试实例,请输出一共有多少种发生这种情况的可能,每个实例的输出占一行。

样例输入
2
2 2
3 2

样例输出
1
3

分析:
这个和之前的小球错放问题很相似,有m个新郎找错了新娘就表示剩下的n-m对是正确的,这n-m对不影响结果,那么这就变成了一个m错排问题,Dn=(n-1)(D[n-2]+D[n-1])(该式的解释在第四题)不同的是这m个可以视为是事先从n个里边选出来的,那么就有Cnm种选法
即:总数=Cnm
Dn=(n!/(m!)!*(n-m)!)*Dn

参考代码:

#include<iostream>
using namespace std;
const int mod = 1e9 + 7;
long long a[21];
long long fun(int n)
{
	long long y = n;
	if(n==0)
		return 1;
	for (int i = 1; i < n; i++)
		y *= i;
	return y;
}
int main(void)
{
	a[1] = 0, a[2] = 1;
	for (int i = 3; i < 21; i++)
		a[i] = (i - 1) * (a[i - 1] + a[i - 2]);
	int T;
	cin >> T;
	while (T--)
	{
		int n, m;
		cin >> n >> m;
		long long rz = fun(n) / (fun(m) * fun(n - m));
		long long ans = (rz * a[m]) % mod;
		cout << ans << endl;
	}
	return 0;
}

应当注意的是long long 类型与int类型运算时,有时会出现数值错误

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值