第三章 递推算法
本章内容小节
递推算法
递推法是一种重要的数学方法,在数学的各个领域中都有广泛的运用,也是计算机用于数值计算的一个重要算法。这种算法特点是:一个问题的求解需一系列的计算,在已知条件和所求问题之间总存在着某种相互联系的关系,在计算时,如果可以找到前后过程之间的数量关系(即递推式),那么,从问题出发逐步推到已知条件,此种方法叫逆推。无论顺推还是逆推,其关键是要找到递推式。这种处理问题的方法能使复杂运算化为若干步重复的简单运算,充分发挥出计算机擅长于重复处理的特点。
递推算法的首要问题是得到相邻的数据项间的关系(即递推关系)。递推算法避开了求通项公式的麻烦,把一个复杂的问题的求解,分解成了连续的若干步简单运算。一般说来,可以将递推算法看成是一种特殊的迭代算法。
题解
T1312 : 昆虫繁殖
【题目描述】
科学家在热带森林中发现了一种特殊的昆虫,这种昆虫的繁殖能力很强。每对成虫过x个月产y对卵,每对卵要过两个月长成成虫。假设每个成虫不死,第一个月只有一对成虫,且卵长成成虫后的第一个月不产卵(过X个月产卵),问过Z个月以后,共有成虫多少对?0≤X≤20,1≤Y≤20,X≤Z≤50。
【输入】
x,y,z的数值。
【输出】
过Z个月以后,共有成虫对数。
【输入样例】
1 2 8
【输出样例】
37
【答案&代码】
#include<stdio.h>
long long adult[64],born[64];
int main(void){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
for(int i=1;i<=x;i++)
adult[i]=1,born[i]=0;
for(int i=x+1;i<=z+1;i++)
born[i]=y*adult[i-x],
adult[i]=adult[i-1]+born[i-2];
printf("%lld",adult[z+1]);
return 0;
}
T1313 : 位数问题
【题目描述】
在所有的N位数中,有多少个数中有偶数个数字3?由于结果可能很大,你只需要输出这个答案对12345取余的值。
【输入】
读入一个数N。
【输出】
输出有多少个数中有偶数个数字3。
【输入样例】
2
【输出样例】
73
【答案&代码】
#include<stdio.h>
int f[1001][2];
int main(void){
int n;
f[1][0]=9,f[1][1]=1;
scanf("%d",&n);
for(int i=2,x;i<=n;i++){
x=f[1][0];
if(i==n)
x--;
f[i][0]=(f[i-1][0]*x+f[i-1][1])%12345,
f[i][1]=(f[i-1][1]*x+f[i-1][0])%12345;
}
printf("%d",f[n][0]);
return 0;
}
T1314: 过河卒
【题目描述】
棋盘上A点有一个过河卒,需要走到目标B点。卒行走的规则:可以向下、或者向右。同时在棋盘上的某一点有一个对方的马(如C点),该马所在的点和所有跳跃一步可达的点称为对方马的控制点,如图3-1中的C点和P1,……,P8,卒不能通过对方马的控制点。棋盘用坐标表示,A点(0,0)、B点(n, m) (n,m为不超过20的整数),同样马的位置坐标是需要给出的,C≠A且C≠B。现在要求你计算出卒从A点能够到达B点的路径的条数。
【输入】
给出n、m和C点的坐标。
【输出】
从A点能够到达B点的路径的条数。
【输入样例】
8 6 0 4
【输出样例】
1617
【答案&代码】
#include<stdio.h>
unsigned long long dp[21][21]={0};
int main(void){
int n,m;
scanf("%d%d",&n,&m);
int mx,my;
scanf("%d%d",&mx,&my);
dp[0][0]=1;
for(int i=0;i<=n;i++){
for(int j=0;j<=m;j++)
if(i==mx&&j==my||
i==mx-1&&j==my-2||
i==mx-2&&j==my-1||
i==mx-2&&j==my+1||
i==mx+1&&j==my-2||
i==mx+2&&j==my-1||
i==mx+1&&j==my+2
)
dp[i][j]=0;
else if(i==0&&j!=0)
dp[i][j]=dp[i][j-1];
else if(j==0&&i!=0)
dp[i][j]=dp[i-1][j];
else if(i==0&&j==0)
dp[i][j]=1;
else
dp[i][j]=dp[i-1][j]+dp[i][j-1];
}
printf("%lld",dp[n][m]);
return 0;
}
T1188: 菲波那契数列(2)
【题目描述】
菲波那契数列是指这样的数列: 数列的第一个和第二个数都为1,接下来每个数都等于前面2个数之和。
给出一个正整数a,要求菲波那契数列中第a个数对1000取模的结果是多少。
【输入】
第1行是测试数据的组数n,后面跟着n行输入。每组测试数据占1行,包括一个正整数a(1 ≤ a ≤ 1000000)。
【输出】
n行,每行输出对应一个输入。输出应是一个正整数,为菲波那契数列中第a个数对1000取模得到的结果。
【输入样例】
4
5
2
19
1
【输出样例】
5
1
181
1
【答案&代码】
#include<stdio.h>
int fun(int n){
int F1=1,F2=1,Fn=1;
for(int i=3;i<=n;i++)
Fn=F1+F2,F1=F2,F2=Fn,Fn%=1000,F1%=1000,F2%=1000;
return Fn;
}
int main(void){
int n;
scanf("%d",&n);
for(int i=0;i<n;i++){
int temp;
scanf("%d",&temp);
printf("%d\n",fun(temp)%1000);
}
return 0;
}
T1189: Pell数列
【题目描述】
Pell数列a1,a2,a3,…的定义是这样的,a1=1,a2=2,…,an=2an-1+an-2(n>2)。
给出一个正整数k,要求Pell数列的第k项模上32767是多少。
【输入】
第1行是测试数据的组数n,后面跟着n行输入。每组测试数据占1行,包括一个正整数k (1≤k<1000000)。
【输出】
n行,每行输出对应一个输入。输出应是一个非负整数。
【输入样例】
2
1
8
【输出样例】
1
408
【答案&代码】
#include<stdio.h>
unsigned int a[1000005]={0,1,2};
int main(void){
for(int i=3;i<=1000000;i++)
a[i]=(a[i-1]<<1)+a[i-2],a[i]%=32767;
int n;
scanf("%d",&n);
for(int i=0,temp;i<n;i++){
scanf("%d",&temp);
printf("%d\n",a[temp]);
}
return 0;
}
T1190: 上台阶
【题目描述】
楼梯有n(71>n>0)阶台阶,上楼时可以一步上1阶,也可以一步上2阶,也可以一步上3阶,编程计算共有多少种不同的走法。
【输入】
输入的每一行包括一组测试数据,即为台阶数n。最后一行为0,表示测试结束。
【输出】
每一行输出对应一行输入的结果,即为走法的数目。
【输入样例】
1
2
3
4
0
【输出样例】
1
2
4
7
【答案&代码】
#include<stdio.h>
long long d[110];
int main(void){
d[1]=1,d[2]=2,d[3]=4;
for(int i=4;i<=100;i++)
d[i]=d[i-1]+d[i-2]+d[i-3];
int n;
while(scanf("%d",&n)==1&&n)
printf("%lld\n",d[n]);
return 0;
}
T1191: 流感传染
【题目描述】
有一批易感人群住在网格状的宿舍区内,宿舍区为n*n的矩阵,每个格点为一个房间,房间里可能住人,也可能空着。在第一天,有些房间里的人得了流感,以后每天,得流感的人会使其邻居传染上流感,(已经得病的不变),空房间不会传染。请输出第m天得流感的人数。
【输入】
第一行一个数字n,n不超过100,表示有n*n的宿舍房间。
接下来的n行,每行n个字符,’.’表示第一天该房间住着健康的人,’#’表示该房间空着,’@’表示第一天该房间住着得流感的人。
接下来的一行是一个整数m,m不超过100。
【输出】
输出第m天,得流感的人数。
【输入样例】
5
....#
.#.@.
.#@..
#....
.....
4
【输出样例】
16
【答案&代码】
#include<stdio.h>
char map[101][102],temp[101][102];
#include<string.h>
int main(void){
int n,m;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%s",map[i]+1);
scanf("%d",&m);
for(int l=1;l<m;l++){
memcpy(temp,map,sizeof(map));
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(temp[i][j]=='.'&&
(temp[i-1][j]=='@'
||temp[i+1][j]=='@'
||temp[i][j-1]=='@'
||temp[i][j+1]=='@'
)
)
map[i][j]='@';
}
int sum=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(map[i][j]=='@')
sum+=1;
printf("%d",sum);
return 0;
}
T1192: 放苹果
【题目描述】
把M个同样的苹果放在N个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法?(用K表示)5,1,1和1,5,1 是同一种分法。
【输入】
第一行是测试数据的数目t(0≤t≤20)。以下每行均包含二个整数M和N,以空格分开。1≤M,N≤10。
【输出】
对输入的每组数据M和N,用一行输出相应的K。
【输入样例】
1
7 3
【输出样例】
8
【答案&代码】
#include<stdio.h>
int f[101][101];
int main(void){
int n,m,t;
for(int i=0;i<=100;i++)
for(int j=0;j<=100;j++)
if(i==0||j==1)
f[i][j]=1;
else
if(j>i)
f[i][j]=f[i][i];
else
f[i][j]=f[i][j-1]+f[i-j][j];
scanf("%d",&t);
for(int i=0;i<t;i++)
scanf("%d%d",&m,&n),
printf("%d\n",f[m][n]);
return 0;
}
T1193: 吃糖果
【题目描述】
名名的妈妈从外地出差回来,带了一盒好吃又精美的巧克力给名名(盒内共有 N 块巧克力,0 < N < 20)。妈妈告诉名名每天可以吃一块或者两块巧克力。假设名名每天都吃巧克力,问名名共有多少种不同的吃完巧克力的方案。例如:如果N=1,则名名第1天就吃掉它,共有1种方案;如果N=2,则名名可以第1天吃1块,第2天吃1块,也可以第1天吃2块,共有2种方案;如果N=3,则名名第1天可以吃1块,剩2块,也可以第1天吃2块剩1块,所以名名共有2+1=3种方案;如果N=4,则名名可以第1天吃1块,剩3块,也可以第1天吃2块,剩2块,共有3+2=5种方案。现在给定N,请你写程序求出名名吃巧克力的方案数目。
【输入】
输入只有1行,即整数N。
【输出】
输出只有1行,即名名吃巧克力的方案数。
【输入样例】
4
【输出样例】
5
【答案&代码】
#include<stdio.h>
int f[32]={0,1,2,3,5};
int fun(int n){
if(f[n]!=0)
return f[n];
else if(n==0)
return 0;
else
return f[n]=fun(n-1)+fun(n-2);
}
int main(void){
int n;
scanf("%d",&n);
printf("%d",fun(n));
return 0;
}
T1194: 移动路线
【题目描述】
X桌子上有一个m行n列的方格矩阵,将每个方格用坐标表示,行坐标从下到上依次递增,列坐标从左至右依次递增,左下角方格的坐标为(1,1),则右上角方格的坐标为(m,n)。
小明是个调皮的孩子,一天他捉来一只蚂蚁,不小心把蚂蚁的右脚弄伤了,于是蚂蚁只能向上或向右移动。小明把这只蚂蚁放在左下角的方格中,蚂蚁从
左下角的方格中移动到右上角的方格中,每步移动一个方格。蚂蚁始终在方格矩阵内移动,请计算出不同的移动路线的数目。
对于1行1列的方格矩阵,蚂蚁原地移动,移动路线数为1;对于1行2列(或2行1列)的方格矩阵,蚂蚁只需一次向右(或向上)移动,移动路线数也为1……对于一个2行3列的方格矩阵,如下图所示:
(2,1) | (2,2) | (2,3) |
---|---|---|
(1,1) | (1,2) | (1,3) |
蚂蚁共有3种移动路线:
路线1:(1,1) → (1,2) → (1,3) → (2,3)
路线2:(1,1) → (1,2) → (2,2) → (2,3)
路线3:(1,1) → (2,1) → (2,2) → (2,3)
【输入】
输入只有一行,包括两个整数m和n(0 < m+n ≤ 20),代表方格矩阵的行数和列数,m、n之间用空格隔开。
【输出】
输出只有一行,为不同的移动路线的数目。
【输入样例】
2 3
【输出样例】
3
【答案&代码】
#include<stdio.h>
long long map[32][32];
int main(void){
for(int i=1;i<=21;i++)
map[i][1]=map[1][i]=1;
for(int i=2;i<=31;i++)
for(int j=2;j<=21;j++)
map[i][j]=map[i-1][j]+map[i][j-1];
int m,n;
scanf("%d%d",&m,&n);
printf("%lld",map[m][n]);
return 0;
}
T1195: 移动路线
【题目描述】
一个给定的正整数序列,在每个数之前都插入+号或-号后计算它们的和。比如序列:1、2、4共有8种可能的序列:
(+1) + (+2) + (+4) = 7
(+1) + (+2) + (-4) = -1
(+1) + (-2) + (+4) = 3
(+1) + (-2) + (-4) = -5
(-1) + (+2) + (+4) = 5
(-1) + (+2) + (-4) = -3
(-1) + (-2) + (+4) = 1
(-1) + (-2) + (-4) = -7
所有结果中至少有一个可被整数k整除,我们则称此正整数序列可被k整除。例如上述序列可以被3、5、7整除,而不能被2、4、6、8……整除。注意:0、-3、-6、-9……都可以认为是3的倍数。
【输入】
输入的第一行包含两个数:N(2< N < 10000)和k(2 < k < 100),其中N代表一共有N个数,k代表被除数。第二行给出序列中的N个整数,这些整数的取值范围都0到10000之间(可能重复)。
【输出】
如果此正整数序列可被k整除,则输出YES,否则输出NO。(注意:都是大写字母)
【输入样例】
3 2
1 2 4
【输出样例】
NO
【答案&代码】
#include<stdio.h>
bool f[10005][205];
int a[10005];
int main(void){
int n,k;
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
scanf("%d",a+i);
f[1][(a[1]%k+k)%k]=1;
for(int i=2;i<=n;i++)
for(int j=0;j<k;j++)
f[i][j]=(f[i-1][((j-a[i])%k+k)%k]||f[i-1][(j+a[i])%k]);
if(f[n][0])
printf("YES");
else
printf("NO");
return 0;
}
T1196: 踩方格
【题目描述】
有一个方格矩阵,矩阵边界在无穷远处。我们做如下假设:
每走一步时,只能从当前方格移动一格,走到某个相邻的方格上;
走过的格子立即塌陷无法再走第二次;
只能向北、东、西三个方向走;
请问:如果允许在方格矩阵上走n步,共有多少种不同的方案。2种走法只要有一步不一样,即被认为是不同的方案。
【输入】
允许在方格上行走的步数n(n≤20)。
【输出】
计算出的方案数量。
【输入样例】
2
【输出样例】
7
【答案&代码】
#include<stdio.h>
int a[21]={0,3,7};
int main(void){
int n;
scanf("%d",&n);
for(int i=3;i<=n;i++)
a[i]=2*a[i-1]+a[i-2];
printf("%d",a[n]);
return 0;
}
T1197: 山区建小学
【题目描述】
政府在某山区修建了一条道路,恰好穿越总共m个村庄的每个村庄一次,没有回路或交叉,任意两个村庄只能通过这条路来往。已知任意两个相邻的村庄之间的距离为di(为正整数),其中,0 < i < m。为了提高山区的文化素质,政府又决定从m个村中选择n个村建小学(设0 < n ≤ m < 500)。请根据给定的m、n以及所有相邻村庄的距离,选择在哪些村庄建小学,才使得所有村到最近小学的距离总和最小,计算最小值。
【输入】
第1行为m和n,其间用空格间隔
第2行为m−1 个整数,依次表示从一端到另一端的相邻村庄的距离,整数之间以空格间隔。
例如:
10 3
2 4 6 5 2 4 3 1 3
表示在10个村庄建3所学校。第1个村庄与第2个村庄距离为2,第2个村庄与第3个村庄距离为4,第3个村庄与第4个村庄距离为6,…,第9个村庄到第10个村庄的距离为3。
【输出】
各村庄到最近学校的距离之和的最小值。
【输入样例】
10 2
3 1 3 1 1 1 1 1 3
【输出样例】
18
【答案&代码】
#include<stdio.h>
int f[510][510],dis[510][510],d[510],s[510][510];
int min(int a,int b){
return a<b?a:b;
}
int abs(int n){
return n>0?n:(-n);
}
int dist(int i,int j){
int x=0;
int mid=(i+j)/2;
for(int k=i;k<=j;k++)
x+=dis[k][mid];
return x;
}
int main(void){
int m,n;
scanf("%d%d",&m,&n);
for(int i=2,temp;i<=m;i++)
scanf("%d",&temp),
d[i]=d[i-1]+temp;
for(int i=1;i<=m;i++)
for(int j=1;j<=m;j++)
if(i==j)
dis[i][j]=0;
else
dis[i][j]=dis[j][i]=abs(d[j]-d[i]);
for(int i=1;i<=m;i++)
for(int j=1;j<=m;j++)
s[i][j]=dist(i,j);
for(int i=1;i<=m;i++)
for(int j=1;j<=m;j++)
f[i][j]=0X3F3F3F3F;
for(int i=1;i<=m;i++)
f[i][i]=0,f[i][1]=s[1][i];
for(int i=2;i<=m;i++)
for(int j=2;j<=min(i,n);j++)
for(int k=j-1;k<=i-1;k++)
if(i!=j)
f[i][j]=min(f[i][j],f[k][j-1]+s[k+1][i]);
printf("%d",f[m][n]);
return 0;
}