UyHiP 往期趣题整理(5)2017 / 01~04

2017-01

请用 O(n) 的时间输出区间 [1, n] 内的所有素数。

(这里需要做一些假设:运行程序的计算机有无限空间,且只能存储区间 [0, n] 内的整数;整数的输出、比较大小、加减乘除等操作只需耗费常数时间(这只是理想化的假设,当 n 很大时这一假设就不成立了)。类似的假设在 UyHiP 的算法题中经常使用,参考这里。)

2017-02

请用 o(n) 的时间输出区间 [1, n] 内的所有素数。

(其余假设同上,注意是小写的 o !)

2017-03

请写出一个 R→R 的无穷阶可导的函数 f,它的 10 阶导数等于自身,且低于 10 阶的所有导数都不等于自身。

2017-04

“2048 推箱子”游戏玩法如下:

游戏在一个四面无穷延展的二维正方形网格进行,初始时每个格子要么为空,要么有一个带有数字 1 的箱子。箱子总数为 2^n,其中 n 是正整数。玩家操控的小人位于某个空格子上。小人每次行动可以向上下左右四个方向之一走一格,但要满足下面三个条件之一才允许:

(1)小人走到的格子是空地;

(2)小人走到的格子是箱子,箱子再往前一格是空地,此时小人走到新格子后箱子也要前移一格,数字不变;(前两条跟推箱子的玩法一样)

(3)小人走到的格子是箱子,箱子再往前一格是带有相同数字的箱子,此时小人走到新格子后箱子也要前移一格,与格子中原有的箱子合并,数字翻倍。(简单地说就是小人把两个箱子推到一起合体了)

注意如果小人面对前后紧挨着的两个数字不同的箱子,就无法立刻推动箱子了。玩家的目标是将所有箱子合并成一个带有数字 2^n 的箱子,赢得游戏。

请问,若存在一种初始状态(包括箱子和小人的初始位置)使得玩家无论如何也不可能赢得游戏,n 最小应是多少?




答案

2017-01

此题有著名的欧拉筛算法,网上随便就能搜到一大堆资料。

欧拉筛需要维护一个素数表(按从小到大的顺序依次记录素数)和一个 01 数组 flag(用于筛素数,下标范围为 [2, n],flag[k]=1 表示 k 是素数)。素数表初始为空,flag 初始全为 1 。这与通常的筛法一致。

欧拉筛的巧妙之处在于巧妙安排每次筛掉的合数,使每个合数只被筛掉一次。

令 i 从 2 到 n 循环,如果 flag[i]=1 说明 i 是素数,将 i 放进素数表。之后不管 i 是不是素数,都让 p 从小到大的顺序遍历素数表中各个素数(注意现在素数表中有且仅有小于等于 i 的素数),将 flag[p*i] 置为 0,遇到 p 整除 i 或者 p*i 大于 n 的情况就立即终止内层循环。

换句话说,内层循环的 p 只能取“不大于 i 的最小素因子”的素数,这样保证了 p 是 p*i 的最小素因子。每个合数都有唯一的最小素因子,因而只会被筛掉一次。另一方面对每个合数,拿出最小素因子 p 拆成 p*i 的形式,显然 i 中的素因子都不小于 p,从而 i≥p,这样外层循环到 i 时素数表中已经有了 p,可以顺利地筛掉 p*i,即保证每个合数都能筛掉。

代码懒得贴了……

2017-02

解此题的关键是:不必预先构建包含 [1, n] 内所有整数的筛子。显然如果只考虑所有奇数,筛子的大小就只剩下一半了。我们可以更进一步,预先筛出 2, 3, 5, 7 的倍数,其他数存放在筛子中。为此可能需要准备一个包含 [1, 210] 中所有非 2, 3, 5, 7 倍数的整数的表。

这样好像对渐近时间复杂度没什么帮助,那就干脆一条路走到黑好了,我们可以用前 5 个素数,前 6 个素数,……。素数足够多时,它们的乘积就已经超过 n 了,这时也不需要什么筛法了。此时我们不依靠筛子,而只是维护一个表,当考虑前 k 个素数时,表中存放的就是 [1, p_1*p_2*...*p_k] 中所有不能被前 k 个素数整除的整数(其中 p_i 表示第 i 个素数)。

如何由 k-1 的表得到 k 的表,实际上非常简单(并且暴力):把 k-1 的表复制 p_k 份(每张表整体平移一下),得到 [1, p_1*p_2*...*p_k] 中所有不能被前 k-1 个素数整除的整数,然后遍历一次取出所有不能被 p_k 整除的数组成新表就行了。时间复杂度显然由中间得到的那个表的长度决定,容易得到 k 的表长度为 (p_1-1)*...*(p_k-1),时间复杂度也就是它了。

(虽然中间得到的表长度为 (p_1-1)*...*(p_(k-1)-1)*p_k,比这个数稍大点,但是渐近意义上没差)

k=1 的表显然就是 [1],由它可以依次往上得出 k=2, 3, ... 的表,这个表长度增长很快。当表的上限马上要超过 n 时需要注意一下,此时假如做到了 k-1 的表,p_1*...*p_(k-1) 还没达到 n,但是 p_1*...*p_k 就要达到或者超过 n 了,设

r=ceil(n/(p_1*...*p_(k-1)))≤p_k

则下一步只要把表复制 r 份就行,不必复制 p_k 份。这一步的时间复杂度就是 O((p_1-1)*...*(p_(k-1)-1)*r) 。至于构建 k-1 的表的过程,显然所有步骤的时间复杂度加起来都比不上它的零头(严格的不等关系证明懒得写了),从而总时间复杂度也就是它了。代入 r 的表达式,注意渐近意义下 ceil 函数可以去掉,时间复杂度变为 O(n*(1-1/p_1)*...*(1-1/p_(k-1))) 。

众所周知无穷乘积 (1-1/p_1)*...*(1-1/p_(k-1)) 发散到 0,因此算法时间复杂度是 o(n) 的。

2017-03

e^(kx) 的导数是 ke^(kx),因此只要让 k^10=1,即取 k 为 10 次单位根(例如 e^(i*pi/5))就行。式子中暂时出现了复数,把它展开一下取实部或者虚部就行。计算过程如下:

第二行就是满足要求的函数。

2017-04

答案是不存在这样的 n,也就是说无论初始状态如何,玩家一定能赢。

将网格平面放在坐标系中,所有格子与坐标系的整点一一对应,原点放在小人的初始位置。把所有格子按照横纵坐标之和分类,横纵坐标之和为 k 的格子叫做 k 类格。

先让小人一路向左上平推,具体操作是交替按向上→向左→向上→向左→……行走,每次向上都将一个 1 类格的箱子推到 2 类格(如果有箱子的话,下同),向左则将一个 0 类格的箱子推到 -1 类格。注意因为初始时箱子都是 1,不可能有推不动的情况。这样一路平推直到足够远,将所有箱子甩在右下方,此时不可能再有围绕着小人的箱子墙了,小人可以自由在外围行走。

接着小人站在足够靠右下方的 0 类格,用同样的方法向左上平推,直到将 0, 1 类格的所有箱子清干净。随后小人用同样的方法清理 4, 5 类格,8, 9 类格,……,-4, -3 类格,-8, -7 类格,……。同样,不可能有推不动的情况,并且因为箱子总数有限,在有限步内总能完成全部清理过程。这样所有箱子都集中在了 4k+2, 4k+3 类格。

最后的工作就容易多了,既然箱子已经“推开”了,什么事情都好办。例如可以在足够靠上的位置画一条横线,横线上方为箱子储藏区,规定只在横纵坐标均为 3 的倍数的位置放箱子,这样任何时刻都可以带一个箱子在储藏区中任意推行。将所有箱子依次推到横线上方(显然是可以办到的,例如每次推“最靠上的箱子中最靠左的箱子”),再逐个合并即可。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值