递推算法常用来求解多种可能路径,所有解的个数,一般找出通项公式即可,但对于某些较大数据的题目,处理起来可能存在些问题。运行时间最少的一种办法是找出递推公式之后,用离散数学上的求解特征方程的办法得到通项公式,不过貌似不太常用。
**
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种选法
即:总数=CnmDn=(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类型运算时,有时会出现数值错误