写在前面
今天讲一个基本的算法思想递推,所谓递推就是根据当前值能够求出下一个值,比如我们熟悉的斐波那契数列,它规定了前两个数为1,剩下的数由f(n)=f(n-1)+f(n-2)来推导而出,在算法竞赛中,常常抛出一个实际问题,大家需要抽象出数学模型也就是递推式子就能顺利解题目。
奇怪的数列
Problem Description
塔兹米是一个喜欢数学的好孩子。有一天,他发现了一个奇怪的数列:
1,1,2,3,5,8,……
在思考许久之后,聪明如他也没办法算出数列中第N个数字,你能帮助他吗?
Input
有多组测试数据。
每组测试数据有一个整数N(0<N<=40)。
Output
对于每个测试数据,输出数列中第N个数是多少。
每个结果占一行。
SampleInput
1
5
SampleOutput
1
5
思路分析
观察得到题目中的数列为斐波那契数列,数据范围很小,先预处理一遍然后输出就好啦
ACcode
#include <iostream>
#include <algorithm>
using namespace std;
int main(){
int n;
int a[43] = {0,1,1};
for(int i = 3; i <= 42; i++)
a[i] = a[i-1]+a[i-2];
while(cin>>n && n){
cout<<a[n]<<endl;
}
return 0;
}
奇怪的台阶
Problem Description
QAQ在一个楼梯上,QAQ现在位于第0阶,无聊的QAQ发现,以他腿的长度,每次可以上1阶,2阶,3阶,而这个台阶一共有N阶,那么QAQ走到第N阶共有多少种走法?
Input
多组测试数据,每组数据一行,包含一个数字N。(1 <= N <= 30)
Output
对于每组数据:
输出到达第N阶共有多少种走法
SampleInput
1
2
SampleOutput
1
2
思路分析
这道题相对于上一题就有点让人摸不着头脑了,首先我们很容易得到 在0阶时,到达1阶有1种办法,到达2阶有2种办法,到达3阶有4种办法(0->1->2->3)(0->2->3)(0->1->3)(0->3).其实那么每一阶当中有什么规律呢?我们规定f[n]为到达第n阶的方案,我们考虑对于当前状态的上一步状态比如1可以由0一步到达,显然f[1]=f[0+1],而f[2]=f[0+2]+f[1+1],f[3]=f[0+3]+f[1+2]+f[2+1] f[4]=f[1+3]+f[2+2]+f[3+1]…根据状态的转移我们可以得到f[1]=1,f[2]=2,f[3]=4,f[n]=f[n-1]+f[n-2]+f[n-3] (n>3),从题目本身来说,就是考虑当前阶数的可以分别由+1,+2,+3三个操作到达。
ACcode
#include <stdio.h>
int main()
{
int n,i[34]={1,2,4},j,m;
for(j=3;j<34;j++)
i[j]=i[j-1]+i[j-2]+i[j-3];
while(~scanf("%d",&n))
printf("%d\n",i[n-1]);
return 0;
}
我读书少,你们得帮帮我
Problem Description
这是一题简单的题目,考的只是你的数学而已。
我一直都很好奇愚公一家到底有多少人。好吧,CJP说你们会帮我的。
假设愚公家族 每个人的一生是这样度过的:(当他回首往事的时候。。。开个玩笑,请无视) 头 20 年用来生长发育以及挖山,第21年(可以理解为21岁的时候)开始 每年生下一个孩子( 自交,任性, 没妻子, 全生男,且 不考虑死亡),当然还要去挖山。
我们默认愚公1岁的时候为第一年(第21年愚公生下第一胎),求第 N年愚公家族(愚公家族不需要妻子,别考虑太多)有多少人。
Input
有多组测试数据,每组占一行,包括一个数N(0<N<=60),N为第N年。
Output
对于每组测试,输出整数M,M为愚公家族的人数。
SampleInput
1
21
41
SampleOutput
1
2
23
思路分析
同样规定状态f[n]为第n天的人口,那么这一题有怎么样的一个状态转移关系呢?我们看到题目中的一个关键数字20,且从21年以后人数开始发生变化,对于第n+1年,f[n+1]=f[n]+(新增人口),我们考虑一下增加的人口就可以了,那么会增加多少人呢?对于第n+1年来说,前20年的人都具备生育能力了,那显然他们都要生也就是他们的数量翻倍就好,所以得到的式子就是 f[n]=f[n-1]+f[n-20](n>20)
ACcode
#include <stdio.h>
int main()
{
int c[70];
int n, i;
while (scanf("%d", &n) != EOF)
{
for (i = 1; i <= n; i++)
{
if (i <= 20)
c[i] = 1;
else
c[i] = c[i - 1] + c[i - 20];
}
printf("%d\n", c[n]);
}
}
骨牌铺方格
Problem Description
在2×n的一个长方形方格中,用一个1× 2的骨牌铺满方格,输入n ,输出铺放方案的总数.
例如n=3时,为2× 3方格,骨牌的铺放方案有三种,如下图:
Input
输入数据由多行组成,每行包含一个整数n,表示该测试实例的长方形方格的规格是2×n (0<n<=50)。
Output
对于每个测试实例,请输出铺放方案的总数,每个实例的输出占一行。
SampleInput
1
3
2
SampleOutput
1
3
2
思路分析
相信通过上面几题的分析,大家都知道第一步要干什么了,首先还是状态的确定,f[n]为2*n大小矩阵的摆放方案数,再考虑能够一步到达n的状态。通过横着摆两个或者竖着摆一个可以到达当前填充好的矩阵。所以惊人的发现这居然是个斐波那契数列的变种,f[n]=f[n-1]+f[n-2];
ACcode
#include<bits/stdc++.h>
using namespace std;
int main()
{
long long int a[55];
a[1]=1;
a[2]=2;
for(int i=3;i<=50;i++)
a[i]=a[i-1]+a[i-2];
int n;
while (cin>>n)
{
cout<<a[n]<<endl;
}
return 0;
}
杨辉三角
Problem Description
还记得中学时候学过的杨辉三角吗?具体的定义这里不再描述,你可以参考以下的图形:
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
Input
输入数据包含多个测试实例,每个测试实例的输入只包含一个正整数n(1<=n<=30),表示将要输出的杨辉三角的层数。
Output
对应于每一个输入,请输出相应层数的杨辉三角,每一层的整数之间用一个空格隔开,每一个杨辉三角后面加一个空行。
SampleInput
2 3
SampleOutput
1
1 1
1
1 1
1 2 1
思路分析
本题根据杨辉三角的定义出发即可f[i][j]=f[i-1][j]+f[i-1][j-1],注意一下边界条件,不过多赘述。
ACcode
#include<stdio.h>
int main()
{
int a, b, c;
int x[50][50];
while (~scanf("%d", &a))
{
for (b = 1; b < 50; b++)
{
x[b][1] = 1;
x[b][b] = 1;
}
for (c = 3; c <= a; c++)
for (b = 2; b < c; b++)
{
x[c][b] = x[c - 1][b] + x[c - 1][b - 1];
}
for (c = 1; c <= a; c++)
{
for (b = 1; b < c; b++)
printf("%d ", x[c][b]);
printf("%d\n", x[c][b]);
}
printf("\n");
}
return 0;
}
武士之魂2:不是假发是桂!
Problem Description
在这天人当道的时代,却依然有着那么一些武士为了推翻昏庸的政府而不懈努力着。桂正是其中之一。但是他却被真选组的人小看了,他们还用“假发”这个外号称呼他以此来激怒他。
“不是假发,是桂!”桂愤怒的大喊。终于他决定给不可一世的真选组一点教训。他制定了一个完美的作战方案——潜入真选组,把他们所有厕所的厕纸全部翻转放置!从而让他们陷入抽不完的厕纸地狱!对于真选组来说,你可以用刀切断厕纸让厕纸可以取出!但是要用刀切断厕纸你身为剑士的尊严就该遭到侮辱!但如果没擦干净你也会被人鄙视!这样一个两难的抉择会打击真选组的积极性!这样兵不血刃的方法也只有桂可以想出来吧!真不愧是被称为狂乱贵公子的男人!
但是真选组的防守也是很严密的,桂必须抓紧时间行动,真选组的本部的地形可以看做多个矩形组成左下角的点为入口,右上角的点为出口,矩形的边即为路径,但是桂并不知道真选组具体有多大,为了能安全的走到出口,桂将只会沿着路向上或者向右走,并翻转沿路厕所里的厕纸。这样一来,桂就有很多的路线可以选择,他是一个追求完美的人,他想知道他会因此少翻转多少的厕纸,所以他必须要算出总共有多少可选路线,但是面对即将到来的潜入,由于这个数可能会很大,因此他已无暇去想了,这就只能由你来帮他计算了。
Input
首先输入一个整数 N ,代表接下来有 N 组测试样例,接下来 N 行输入一个整数 M ,代表真选组的地形为 M * M 的矩形。 1<=M<=10 。
Output
输出有多少条可选的路线。
SampleInput
2
1
2
SampleOutput
2
6
思路分析
告诉你起点(1,1)每次可以向右走或者向下走问你到达(n,m)的方案数,首先还是规定状态f[i][j]为到达(i,j)的方案,再考虑能一步到达(i,j)的情况即为(i,j-1)(向右走) (i-1,j)(向下走),很明显能一步到达(i,j)的状态,(i,j)也能一步回去,题目给我们的是向右和向下,所以我们只要在当前格向左或者向上就可以找到对应的状态,所以本题状态转移方程为f[i][[j]=f[i-1][j]+f[i][j-1];
ACcode
# include<stdio.h>
int main()
{
int dp[11][11],i,j;
int t, n;
scanf("%d", &t);
while (t--)
{
scanf("%d", &n);
for (i = 1; i <= n; i++)
{
dp[i][0] = 1;
dp[0][i] = 1;
}
for (i = 1; i <= n; i++)
{
for (j = 1; j <= n; j++)
{
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
printf("%d\n", dp[n ][n]);
}
return 0;
}
总结
大家可以发现,所谓递推题,就是需要找到一个方程来转移状态,那么我们首先的步骤就是用数组表达状态,然后处理一下边界条件,也就是最开始的起点,然后接下来所有状态根据题目来考虑,当前状态的所有上一个状态,累加即可,这一思路其实就是动态规划的思想,希望大家好好思考这些问题,随着题目的复杂化,这些状态转移可能会难以看出,或者很难转移,我们又会学习更新的办法来解决。