-
-
[原创] CodinGame最高难度Nintendo挑战题
-
发表于: 1小时前 49
-
几年前在CodinGame上遇到过一道没解出的题。最近再看到,思路阔然开朗,感慨颇多。
题目是一段简单代码,核心只有一行表达式(原题见附件):

输出 array b 为:

要求反推输出 array a
一直觉得相关专业的教材,行文逻辑对新人极不友好。比如抽象代理中介绍RSA原理,开篇就是:
给定两个大素数 p 和 q
这不是在解释密码学原理,只是凭空给到的实现工序。如果诚心教学,应该从设计初衷讲起:
message encrypt = cipher
cipher decrypt = message
有了最简PoC之后 => 再披露安全缺陷 => 然后才有动机引入欧拉函数,而不是颠倒过来讲。至于AI,在稍复杂数学问题上的解释能力,更是惨不忍睹。
我无法去改变行业现状,只能轮到自己写writes up的时候,更用心些。
i j 双循环即是二维平面,每个格子代表了第i列、第j行,直观来看代码逻辑就是在遍历棋盘。

不难看出,a[i/32] >> (i%32) 中的 i/32 代表了入参的第几个block、 i%32 代表了第几个block下的偏移量,合在一起就是在取整体入参序列的第 i 位(j同理)。

... & ... & 1 可以理解为取行、列值同时为1的格。

b[(i+j)/32] ^= ... << ((i+j)%32) ,可以理解为上述格在斜线上的求和(因为同一斜线中每个格子的 i+j 恒定)。

至此,我们对这段代码的几何意义有了直觉理解。接下来要做的就是配平这枚魔方。
几何理解只是一个锚点,他帮助我们产生进一步联想。看到这个纺锤状的图形,很容易回忆起小学时的乘法竖式。
我们列出 x3x2x1x0 * y3y2y1y0 的竖式,体会下

不难发现:末行的表达式恰好贴合本题的代码逻辑(只不过这里的加法需要重定义为不进位加法,即:异或)。
那么,这份代码本质上在做以下事情:
输出B = 输入A的前半段 x 输入A的后半段
至此,课题退化为已知公案:大数因数分解“易题”(与RSA的自然数分解不同,由于这里乘法不带进位,应属于伽罗瓦域运算)
从这里开始,解题思路走到了一个关键的岔路口,摆在面前有两个方向。
我认为本手就应该继续配平魔方,机制很明确:
每拨动1位横坐标+1位纵坐标,会产生1个新增黑点,同时产生若干灰点(余数)。
多个余数组合,又恰好产生新的黑点。
核心命题是如何在这套体系下,归纳出能独立扰动单格输出槽位的公式。这是人类最能直观理解的方向。
另外一种取巧的方向,是直接复用整数环上的因数分解攻击方案,在本题的语境下做合理外推。
套用既有公式得到端到端的答案,代价是丢失对计算过程的直觉理解。
碍于精力有限,我暂时选了方向 2。
但我们很难评判,鸡兔同笼的巧思,被解方程这种工具彻底取代后,到底是人类思想的进步还是退化。
我粗浅的理解是,数学只是人类对世界规律的有损压损,不是世界本身。如果不回归到生活经验,最终会走向歧途。
因数分解问题主要有三个攻击方案(不考虑Shor算法)
Pollard Rho(差速指针)
二次筛法
数域筛法
理论上RSA的攻击方案,在重载操作符后,对本题都应该适用。
我最先尝试了 Pollard Rho,由于算法本身存在概率缺陷,解不了本题。
紧接着尝试二次筛法。在伽罗瓦域上确实存在一个与二次筛法思路接近的方案,即:Berlekamp算法。
让我们从零开始反推下Berlekamp算法的设计思路。
把 B 分解为 A1 * A2
如果我们想要地毯式搜寻 A1 * A2 这株独苗,命中概率显然小得可怜。有没有可能放宽条件呢?
可以尝试先找到A1的一个倍数,与B辗转相除求最大公约数,这样也能得出A1(平凡因子除外)
如此,问题从找A1,转变为找n倍的A1,靶面立刻被放大了无数倍。
这类倍数思想在素数领域很常见,所以最先容易联想到。比如最简单的判断素数方法,就是在自然数序列中,把2的倍数、3的倍数、4的倍数依次拉黑,那么剩余没人认领的数就是素数。

冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。