C++ 算法篇 递推

 递推 

“递推”是计算机解题的一种常用方法。利用“递推法”解题首先要分析归纳出“递推关系”。如经典的斐波那契数列问题,用 f (i)表示第 i 项的值,则 f (1) =0,f(2) =1,在 n>2 时,存在递推关系:f (n) = f(n-1) + f(n-2)。
     在递推问题模型中,每个数据项都与它前面的若干个数据项(或后面的若干个数据项)存在一定的关联,这种关联一般是通过一个“递推关系式”来描述的。求解问题时,需要从初始的一个或若干数据项出发,通过递推关系式逐步推进,从而推导计算出最终结果。这种求解问题的方法叫“递推法”。其中,初始的若干数据项称为“递推边界”。
解决递推问题有三个重点:
1、建立正确的递推关系式;
2、分析递推关系式的性质;
3、根据递推关系式编程求解。
递推法分为“顺推”和“倒推”两类模型:
1、顺推,就是从问题的边界条件(初始状态)出发,通过递推关系式依次从前往后递推出问题的解;
2、倒推,就是在不知道问题的边界条件(初始状态)下,从问题的最终解(目标状态或某个中间状态)出发,反过来推导问题的初始状态。

例1、一个数字三角形

请编一个程序计算从顶到底的某处的一条路径,使该路径所经过的数字总和最大。只要求输出总和。  

 1、 一步可沿左斜线向下或右斜线向下走;  

 2、 三角形行数小于等于100;

3、 三角形中的数字为0,1,…,99;    

测试数据通过键盘逐行输入,如上例数据应以如下所示格式输入:

5

7

3 8

8 1 0

2 7 4 4

4 5 2 6 5

【算法分析】 此题解法有多种,从递推的思想出发,设想,当从顶层沿某条路径走到第i层向第i+1层前进时,我们的选择一定是沿其下两条可行路径中最大数字的方向前进,为此,我们可以采用倒推的手法,设a[i][j]存放从i,j 出发到达n层的最大值,则a[i][j]=max{a[i][j]+a[i+1][j],a[i][j]+a[i+1][j+1]},a[1][1] 即为所求的数字总和的最大值。

例1、铺瓷砖

【问题描述】

用红色的 1×1 和黑色的 2×2 两种规格的瓷砖不重叠地铺满 n×3 的路面,求出有多少种不同的铺设方案。
【输入格式】
一行一个整数 n,0<n<1000。
【输出格式】
一行一个整数,为铺设方案的数量模12345的结果。
【输入样例】
2
【输出样例】
3
【问题分析】
f(n) 表示 n×3 的路面有多少种不同的铺设方案。把路面看成 n 3 列,则问题可以分成两种情况考虑,一种是最后一行用 3 1×1 的瓷砖铺设;另一种是最后两行用 1 2×2 2 1×1 瓷砖铺设(最后两行就有两种铺法),第一种铺法就转换为 f(i-1) 问题了,第二种铺法就转换成 f(i-2) 的问题了。根据加法原理,得到的递推关系式为 f(i) = f(i-1) +f(i-2)×2,边界为 f(0)=1f(1)=1

 例2、彩带 

【问题描述】
        一中 90 周年校庆,小林准备用一些白色、蓝色和红色的彩带来装饰学校超市的橱窗,他希望满足以下两个条件:
(1) 相同颜色的彩带不能放在相邻的位置;
(2) 一条蓝色的彩带必须放在一条白色的彩带和一条红色的彩带中间。
    现在,他想知道满足要求的放置彩带的方案数有多少种。例如,如图 9.4-1 所示为橱窗宽度n=3 的所有放置方案,共 4 种。
【输入格式】
一行一个整数 n,表示橱窗宽度(或者说彩带数目)
【输出格式】
一行一个整数,表示装饰橱窗的彩带放置方案数。
【样例输入】
3
【样例输出】
4
【数据规模】
30% 的数据满足:1n15
100% 的数据满足:1n45
【问题分析】
         f (i) 表示宽度为 i 的橱窗( i 条彩带)的合法放置方案数, f(1) =2f(2) =2f(3) =4f(4) =6f(5) =10……不难发现,答案就是初始值不一样的斐波那契数列,所以,用递推法就可以很方便地求出 f (n)

 例3、城市路径

【问题描述】
  
        地图上有 n 个城市,一只奶牛要从 1 号城市开始依次经过这些城市,最终到达 n 号城市。但是这只奶牛觉得这样太无聊了,所以它决定跳过其中的一个城市(但是不能跳过 1 号和 n 号城),使得它从 1 号城市开始,到达 n 号城市所经过的总距离最小。假设每一个城市 i 都有一个坐标(x i y i ),从 (x 1 y 1 ) 的城 1 (x 2 y 2 ) 的城市 2 之间的距离为 | x 1 -x 2 | + | y 1 -y 2 |
【输入格式】
1 1 个正整数 n,表示城市个数。
接下来的 n 行,每行 2 个数 x i  y i ,表示城市 i 的坐标。
【输出格式】
一行一个数,使得它从1号城市开始,跳过某一个城市,到达n城市所经过的最小总距离。
【输入样例】
4
0 0
8 3
11 -1
10 0
【输出样例】
14
【样例说明】
跳过 2 号城市。
【数据规模】
对于 40% 的数据满足:n1000
对于 100% 的数据满足:3n100000-1000x i y i 1000
【问题分析】
f (i)为从城市1依次跳到城市i的距离之和,设g(i)为从城市i依次跳到城市n的距离之和,则问题的答案为 min{f (i-1)+g(i+1)+dis[i-1,i+1]}。其中,dis [i-1,i+1]表示城市 i-1 到城市 i+1的曼哈顿距离,f (i) g(i)都可以用递推来预处理求出:f(i)= f(i-1)+ dis[i-1,i]g(i) =g(i+1) +dis[I,i+1]
也可以设 f (i)表示从城市 1 依次跳到城市 n,且跳过城市 i 的距离之和,sum 表示表示从城市 1 依次跳到城市 n 的距离之和,则 f (i)=min{sumdis[I,i-1]-dis[i+1.i]+dis[i-1,i+1]}

 例4、穿越沙漠 

【问题描述】
一辆卡车欲穿过 1000 千米的沙漠,卡车耗油为 1 / 千米,卡车总载油能力为 500 升。显然,卡车装一次油是过不了沙漠的。因此,司机必须设法在沿途建立几个贮油点,使卡车能顺利穿越沙漠。试问:司机如何建立这些贮油点?每一贮油点应存多少油,才能使卡车以消耗最少
汽油的代价通过沙漠?结果保留小数点后两位。
编程计算及打印建立的贮油点序号,各贮油点距沙漠边沿出发的距离以及存油量,格式如下:
No.   Distance(km)    Oil (litre)
1       ×××.××              ×××.××
2       ×××.××              ×××.××
3       ×××.××              ×××.××
【问题分析】
因为从起始点出发无法确定第 1 个贮油点的位置及贮油量,所以顺推行不通。下面换个思路倒推试试看。先从终点出发倒推最后一个贮油点的位置及贮油量,然后再把最后一个贮油点作为终点,倒推倒数第 2 个贮油点的位置及贮油量,…… dis(i)表示第 i 个贮油点至终点(i=0)的距离,oil (i)表示第 i 个贮油点的贮油量。从终点向起始点倒推,逐一求出每个贮油点的位置及存油量,如 9.4-2 所示。
从贮油点 i 向贮油点 i+1 倒推的策略是,卡车在点 i 和点 i+1 间往返若干次。卡车每次返回i+1 处时正好耗尽 500 升汽油,而每次从 i+1 处出发时又必须装满 500 升汽油。两点之间的距离必须满足在耗油最少的条件下使 i 点贮存 i×500 升汽油的要求(0in-1),根据贪心思想,第一个贮油点 i=1 应距终点 i=0 500 千米且在该处贮藏 500 升汽油,这样才能保证卡车能由 i=1 到达终点 i=0 处,这就是说,dis(1)=500oil(1)=500。而为了在 i=1 处贮藏 500升汽油,卡车至少从 i=2 处开两趟满载油的车至 i=1 处,所以 i=2 处至少存贮 2×500 升汽油,即 oil(2) =500×2=1000,另外再加上从 i=1 返回至 i=2 处的一趟空载,合计往返 3 次,往返路程的耗油量按最省要求只能为500升,即d 1= 500/3,所以dis (2)=dis (1)+d 1=dis (1)+500/3
同理,为了在i=2处贮存1000升汽油,卡车至少从i=3处开3满载油的车至i=2处。所以,i=3 处至少存贮 3×500 升汽油,即 oil(3)=500×3=1500,加上 i=2 i=3 处的 2 趟返程空车,合计 5 次,路途耗油量也应该是 500 升,即 d 23  =500/5,所以 dis(3)=dis(2)+d 2=dis(2)+500/5
依次类推,为了在 i=k 处贮藏 k×500 升汽油,卡车至少从 i=k+1 处开 k 趟满载车至 i=k 处,即oil(k+1)=(k+1)×500=oil (k)+500,加上从 i=k 返回 i=k+1 k-1 趟返程空车,合计 2*k-1 次,总耗油量按最省要求为 500 升,即 d k k+1  = 500/ (2×k-1)所以 dis(k+1) = dis(k)+d kk+1  = dis(k)+500/(2×k-1)
最后一个贮油点的位置如图 9.4-4 所示。
最后,i=n至起始点的距离为1000-dis (n)oil (n)=500×n。为了在i=n处取得n×500升汽油,卡车至少从始点开n+1次满载车至i=n,加上从i=n返回起始点的n趟返程空车,合计2×n+1次,总耗油量应正好为(1000-dis(n))×(2×n+1),即起始点藏油为 oil (n)+(1000-dis(n))×(2×n+1)

例5、偶数个3

【问题描述】
编程求出所有的 n 位数中,有多少个数中有偶数个数字 3
【输入格式】
一行一个正整数 n0<n<1000
【输出格式】
一行一个正整数,表示 n 位数中有多少个数有偶数个 3
【输入样例】
2
【输出样例】
73
【问题分析】
无论一个多位数中有几个 3,都可以分为奇数个和偶数(包括 0)两组,在一个多位数末尾加上一个 3,就会改变其奇偶性,加其他数字都不会改变它的奇偶性。所以,可以 1~9 这九个一位数为基础,采用每次向末尾加一位的方法,逐步构造并达到 n 位数,来讨论它们的奇偶性。
a(i) 存储 i 位数中 3 为奇数的个数,b(i)存储 i 位数中 3 为偶数的个数,则很容易推出递推关系式:a(i) = a(i-1)×9+b(i-1)b(i) = b(i-1)×9+a(i-1),因为首位不为 0,所以边界条件为:a(1) = 1b(1) = 8。程序实现时,也可以用一个数组来优化。

 例6、新约瑟夫游戏 

【问题描述】
一个皆大欢喜的新游戏:假设 n个人站成一圈,从第 1 人开始交替的去掉游戏者,但只是暂时去掉(例如,首先去掉 2),直到最后剩下唯一的幸存者为止。幸存者选出后,所有比幸存者号码高的人每人将得到 1 块钱,并且永久性地离开,其余剩下的人将重复以上过程,比幸存者号码高的人每人将得到 1 块钱后离开。一旦经过这样的过程后,人数不再减少,最后剩下的那些人将得 2 块钱。请计算一下组织者一共要付出多少钱?
如图 9.4-5 所示,第一轮有 5 人,幸存者是 3,所以 45 1 块钱后离开,下一轮幸存者仍然是 3,因此没有人离开,所以每人得到 2 块钱,总共要付出 2+2×3=8 块钱。
【输入格式】
一行一个整数 n,不超过 32767
【输出格式】
一行一个整数,不超过 65535,表示总共要付出多少钱。
【输入样例】
10
【输出样例】
13
【问题分析】
      约瑟夫问题有很多种变形,这是其中的一种。首先,每个人都会得到1块钱,只有最后的那些幸存者多得到了1块钱。所以,我们只要求出最后会幸存几个人便行了。假设经过m次出圈操作后还剩final(m)个人,此时人数不再减少了,则问题的解应该为:final(m)+ n。如何求final(m)呢?显然,当第i次的final(i)=i时,人数就不会再减少了,此时的i即为m;否则,我们就需要对剩下的final(i)个人再进行报数出圈操作。
        jose(i)表示i个人的圈报数后的幸存者编号,设报到k的人出去,则jose(i-1)可以理解为第一轮第一次报数,k出去后的状态。如图9.4-6所示左图,k出去后会从k+1继续报数,此时圈中有i-1人,从k+1开始报数,编号jose(i)为:k+1k+2 i 12k-1
我们可以人为地把这个圈逆时针 转k个单位,即变成图9.4-6所示 右图的状态,此时报数的编号 jose(i-1):12i-k i-k+1
i-k+2i-1
    观察以上两个序列发现,除了加边框的两个数据外,其它所有数据都满足下列规律:jose(i)=(jose(i-1)+ k) mod i。对这个式子稍做调整,变成如下公式就都满足了:jose(i)=(jose(i-1)+1) mod i + 1     至此,我们就找到了问题的递推关系式,边界条件也很明显,就是jose(1)=1。然后,顺推求出每个jose(i),直到某一次jose(i)=i,final(i)赋值为i,否则,final(i)赋值为final(jose(i))
  

  • 6
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值