漫游算法 02 - 模拟

引入

模拟,不过是根据题面打出最直白的程序,是实现最无脑、结果最正确的一种算法。相对其他算法,模拟在复杂度方面会有所逊色,但是用处却不小。例如,先用模拟解决某个问题,并测试多组数据,用其结果探究有何普遍规律可寻。或者对某个算法不确定时,常常会使用一个模拟程序与其对照,从而找出其漏洞或检验其正确性。即使想不出正解,模拟也可以用于骗取部分分数,甚至能得到满分。
然而,模拟的题目一般有大量细节,不慎就会掉进题意的坑里,务必要做到“读题目仔细,码程序专注”


实现

模拟算法如何实现?事实上,引用里已经讲得十分清楚了,就是按着题面的流程打出程序。
但是,模拟算法加一些本质上的优化也是可以的,只要该算法并未脱离题面。
想要验证某一问题完全可用模拟解决,按我的经验,可以这么做:

观察数据范围,构思程序的大致流程,估算模拟的时空复杂度,确保本题使用模拟算法是万无一失的。

当然,当你想用模拟算法验证算法或骗分,以上方法可略去。
确认使用模拟算法后,果断开始码程序。如果发现某部分程序完全可以用更优的程序代替,可以选择代替之。如果某部分程序难以码出,尝试结合样例或画图、列表理解,暂时抛弃时间复杂度,相信模拟是万能的……
最后检查程序是否有细节错误,毕竟模拟算法注重细节,再用样例进行测试。


实例

下面我以两个原创模拟题来进一步介绍模拟的食用方式与常用技巧。

========================================

实例一 . 算筹

题目描述

算筹计数法是我国古代著名的计数法。它以算筹(即竹签)来表示数字,而表示某一位上的数字有纵式和横式两种方法。
具体来说,个位用纵式,十位用横式,百位用纵式,千位用横式,以此类推。 如果某一位上的数字为 0,则留空该位,但并不影响其他位上数字的摆法。其他数字的纵横摆法如下图:

算筹摆法

Kornal 学习了算筹计数法后,按照它用竹签摆出一个数字 n 。你能帮他统计摆出的算筹数字中,多少根竹签是横着的,多少根竹签是竖着的吗?例如,摆出纵式的 8 就需要横着的竹签 1 根,竖着的竹签 3 根。

输入格式

仅有 1 个正整数,表示摆出的数字 n

输出格式

有 2 个整数,用单个空格隔开,分别表示所用横着的竹签数量和竖着的竹签数量。

输入样例

17

输出样例

2 2

样例说明

先摆个位的数字 7,使用纵式,通过查看图,得出需要横竹签 1 根,竖竹签 2 根。 再摆十位的数字 1,使用横式,通过查看图,得出需要横竹签 1 根。 因此,总共需要横竹签 2 根和竖竹签 2 根。

数据范围

对于 25% 的数据,有 n10,000
对于 所有 的数据,有 n10100

按照检验模拟的方法,我们先观察数据范围,可以发现 n10100
接下来开始构思整体流程。显然利用整型来存储如此大的数字,是不可行的。使用高精度呢,处理又比较麻烦。而使用字符串存储数字串,是绰绰有余的。
先避免复杂的思考,思考最最无脑的模拟算法。先将数字输入到字符串里,再循环一遍,处理每个数位,判断该位上的数用横式摆还是纵式摆,并用一系列分支语句分别根据该位上的数字统计不同的答案。
这样的模拟的确没有错,但在实现分支时,未免会有大量麻烦,同时特别容易打错某个数字导致整个程序遭殃。此时模拟的最佳拍档——“打表”,便有了用武之地。
何为打表?简单来说,就是提前手动或用简易程序算出一些值,并将这些值以常量、数组等形式表示在源码中。
回到算筹那题,正可运用打表技巧,提前数出横式和纵式下表示一到九分别需要多少个横竹签和竖竹签,用常量数组存储之。此时,大量分支就被一段简单的变量加所代替,不仅保留了模拟算法,更简化了代码。
本题的打表形式如下,具体用途就不细说了:

const int a1[10] = {0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1};
const int b1[10] = {0 , 1 , 2 , 3 , 4 , 5 , 1 , 2 , 3 , 4};
const int b2[10] = {0 , 1 , 2 , 3 , 4 , 5 , 1 , 2 , 3 , 4};
const int a2[10] = {0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1};

========================================

实例二 . 堆雪人

题目描述

圣诞节,是白雪皑皑的。
天色渐渐暗了下来,随着一片又一片雪花的飘落,老师与同学们的圣诞晚会悄然到来。
洁白的雪令同学们都想到了堆雪人的游戏,因此他们计划举行 t 分钟的堆雪人活动,共 p 个人参加,在晚会前活跃气氛。
下了一整天的雪,地面上已积了满满 n 层雪。从活动开始到结束的每一分钟初,地面都会多积 m 层雪。制作一个雪人需要 k 层积雪。
活动开始到结束的每一分钟末,同学们开始堆雪人。由于人力有限,每分钟末最多一共只能堆 p 个雪人。
Kornal 作为这些同学的代表,想组织同学堆最多的雪人。那么他们最多能堆多少个雪人呢?

输入格式

第 1 行,有 3 个整数,分别表示已积雪层数 n ,每分钟降雪层数 m 以及堆一个雪人需要积雪的层数 k
第 2 行,有 2 个整数,分别表示堆雪人活动有 p 个人参加,且进行了 t 分钟。

输出格式

仅 1 个整数,为堆雪人活动最多能堆的雪人数。

输入样例

3 1 2
1 3

输出样例

3

样例说明

活动将进行 3 分钟,仅有 1 人参加。最初有 3 层雪,每分钟降 1 层雪,堆一个雪人需要 2 层雪。
第 1 分钟初,下了 1 层雪,此时积了 3 + 1 = 4 层雪,虽然 4 层雪可以堆 2 个雪人,但是人力不足,只有 1 个人,因此只能耗费 2 层雪堆 1 个雪人,剩余 4 - 2 = 2 层雪。
第 2 分钟初,下了 1 层雪,此时积了 2 + 1 = 3 层雪,只能耗费 2 层雪堆 1 个雪人,剩余 1 层雪。
第 3 分钟初,又下了 1 层雪,此时积了 1 + 1 = 2 层雪,耗费 2 层雪堆 1 个雪人,剩余 0 层雪。
因此,本次活动最多能堆 3 个雪人。

数据范围

对于 20% 的数据,有 p=1
对于 另外20% 的数据,有 k=1
对于 所有 的数据,有 0n1,000 0m100 1k100 1p100 1t100

解题,仍然是先观察数据范围,而本题的数据范围,告诉我们这题可以用模拟随便玩。
然后是整理流程,我们发现整道题的大致流程可概括为:初始化雪的层数 -> 每分钟初下雪 -> 每分钟末堆雪人。
初始化和每分钟初下雪只通过一两段代码即可实现,至于每分钟末的堆雪人,如何实现更好呢?
最直白的方法,使用一层循环。由于人力的存在,需要循环 p 次,每次判断是否能再堆一个雪人,如果不能,直接退出循环,因为这一分钟已经有雪凭空多出来了。如果能,则统计答案,并减去雪的层数。
有没有更优秀的做法呢?当然有,只需要一个判断即可。此时,模拟的另一个拍档出现了——“列式”
列式和打表类似,只需要推出并运用式子计算出指定的值,即可代替大把大把的循环或分支语句。但推式子需要深思熟虑,务必保证正确性!
回到原题中,堆雪人这一步,设 n 为每分钟剩余的雪层数,可以列出式子计算 g=n/k (此处的除法为整除),此时 g 即可表示那一分钟末的积雪足够堆的雪人数。
然后,判断 g 是否大于等于 p ,因为 p 为每分钟末最多能堆的雪人数,如果每分钟末堆超过 p 个雪人,自然就不满足题面要求了。此时只能堆 p 个雪人,统计入答案,并给剩余的雪减去 p×k 层。
否则呢?就是积雪不足以堆 p 个雪人,而不得不堆 g 个雪人了,统计入答案,并给剩余的雪减去 g×k 层。


结语

那么我们接触的第一种算法“模拟”至此已经较详细地讲完了,不知读者您是否耐心地阅读完了呢。总结一下,模拟作为所有算法的基础,虽然本质上简单,但仍有需要注意之处,更有许多巧妙的用法。在模拟程序中,我们也可以使用 “打表”“列式” 来简化一些繁琐的部分。再次强调,使用模拟时务必做到“读题目仔细,码程序专注”哦!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值