Simplicity is relative.
To the great majority of mankind – mathematical ignoramuses – it is a simple fact,
for instance, that 17 X 17= 289, and a complicated one
that in a principal ideal ring a finite subset of a set E suffices to generate the ideal generated by E.
For the reader and for others among a select few, the reverse is the case.
-- Carl E. Linderholm
标题党一回,这篇文章其实并没有对解题的方法论进行探讨,而只是简单的对Petr最近一篇解题报告进行了一下翻译和整理。我一向不喜欢干翻译这事,但这回要破个例,因为难得有机会能见识到世界顶尖解题高手和算法高手对于一个问题的详细处理过程。
问题是这样的:你首先站在数轴的原点上,然交替地向左和向右移动,即先向左移动一步,再向右移动一步,再向左移动一步…。现在给定两个正整数集合 A 和 B ,限制你在每次向左移动时,移动的距离必须是 A 中的一个元素,而向右移动时,移动的距离必须是 B 中的一个元素。判断你是否能通过这种交替移动到达整数轴上的任意位置。比如当 A = {1}, B = {1} 时,你显然只能在 –1 和 0 两个点徘徊,而当 A = {1, 2}, B = {1, 2}时,此时你却可以轻易的到达任意位置。why? 先思考思考,想明白了就代表你理解题意了。
输入规模限制:集合 A 与 B 的大小不超过 100,000 ,而每个集合元素不大于 1,000,000,000。
首先遇到这个题目的时候,你发现必须要交替移动这个限制很讨厌,影响了你的思路。因此,一个自然的想法就是把先它搞掉。怎么搞?可以把向左的一步与接下来向右的一步合并为一大步。这样就得到一个新的整数集合 C = {b – a | a 属于 A, b 属于 B}, 这个集合包含了所有 B 集合元素与 A 集合元素的差。于是现在题目的限制就变成了,首先你可以移动任意多步,其中每一步所移动的距离为 C 中的任意一个元素(移动的方向由该元素的正负号决定),最后你还可以选择是否向左移动一步,移动的距离可以为 A 中的任意一个元素。
现在先不考虑最后是否向左移动这一步,来看看如果只在 C 中进行移动的话可以达到哪些地方。显然,一个点 p 可以到达当且仅当 p 可以表示成 k1c1 + k2c2 + k3c3 + … 的形式,其中 ci 属于 C,而 ki 是非负整数。到这里 ki 必须非负这一条件又似乎有点麻烦,暂时先不管它,考虑若放宽条件,当 ki 可以取任意正整数时,所有的 p 形成的集合 P = {k1c1 + k2c2 + k3c3….} 是什么样子?
答案很简单,即 P = { kg | k 属于整数集Z} 为所有 g 的倍数所组成的集合,其中 g 为 C 中所有元素的最大公约数。这一结论用数学归纳法很容易证明。倘若你还知道一点抽象代数,那么可以知道 P 实际上是整数环上的一个理想,由于整数环是主理想整环,因此 P 还是个主理想,于是可以由一个元素生成,而这个元素正是 g。(写到这里的时候,又翻了翻龚昇老师的线代五讲,龚老师于2011年1月10日在北京逝世,祝愿老师在天堂走好!)
上面得到的结果可能会让你有继续下去的信心,但是别忘了前面我们忽略了 ki 不可以为负的条件。考虑这样一个问题:对于 C 中的任意一个数 c,你是否可以移动 –c ?如果答案是“可以”,那么 ki 不能为负这个限制就可以安全的去掉。若 C 中的所有元素都是同号的,那么显然对于任意一个 c 你都不可能移动 -c,但此时最初的问题也已经解决了,因为 C 中所有元素同号意味着你最初的若干步都只能往一个方向走,虽然你最后还有一次向左走的机会,但此时败局已定,因此直接返回一个 false 即可。如果 C 中有正有负,即对于任意 c, 存在一个符号以其相反的数 d,那么可以先移动 |c| 次d,再移动 |d| – 1 次 c,总的移动距离为 |c|d + (|d| – 1) c = –c。哈,问题解决,运气不错。
总结一下前面所得到的结论,如果 C 中所有元素都为非正或非负,则可以直接返回 false,否则可以先移动到 g 的任意倍数位置,之后还可以选择是否向左再移动一步。
现在来考虑最后的这一步。由于在前面的移动中已经可以到达 g 的任意倍数位置,于是在经过最后一次移动后你可以到达位置 x 当且仅当 x 可以表示成 kg – a 的形式,其中 a 是 A 中的某个元素。注意到 x 所组成的集合 X = {kg – a} 也可以写成 X = { x | x = -a mod g },因此现在只需要注意 A 中所有元素模 g 后所得到的集合。
由于 g = gcd(a1 – b1, b2 – b1, b3 – b1, …}, 因此 ai – b1 可以被 g 整除。于是 ai – b1 这些元素之间的差 ai – aj 也可以被 g 整除,这就告诉我们:A 中的所有元素模 g 是同余的。也就是说 X 即为 { x | x = –a1 mod g}.
到这里,所有你可能到达的位置都已经找出来了,即所有 g 的倍数(若你选择不移动最后这一步),以及所有模 g 后等于 (– a1 mod g) 的数(如果你选择最后再向左移动一步)。这两类数若想组成整个整数集,只能是以下两种情况:
- g = 1
- g = 2 且 (a1 mod g) = 1
这样我们就得到了解决整个问题的算法如下:
- 判断在 C 中是否有一个正数
- 判断在 C 中是否有一个负数
- 若上两步得到的答案不全为 true,则直接返回 false
- 算出 C 中所有元素的最大公约数 g
- 算出 a1 模 g 的结果 r
- 若 g = 1 或者 g = 2 且 r = 1,返回 ture
- 否则返回 false
问题似乎已经解决了,但是别忘了 C 的大小可能是 10 的 10 次方数量级。在上面的算法中,第 1 步和第 2 步显然不需要完全求出 C 即能得到答案,问题的关键在于第 4 步,怎么算出 C 中所有元素的最大公约数 g? 对于任意给定的一个集合,显然要求出其所有元素的最大公约数必须是 Ω(n) 的,但 C 并不是“任意给定”的集合,它是由所有形如 bi – aj 的元素所组成的。因此我们可以期望利用这个特殊性来寻找一个更快速的方法。
注意到 bi – aj = (bi – b1) + (b1 – a1) + (a1 – aj),构造一个集合 D,其元素为 b1 – a1、 所有的 bi – b1、以及所有的 a1 - aj。若 D 中所有元素的最大公约数为 s,则显然 s 能被 g 整除,因为在前面我们已经知道 A 中所有元素模 g 同余,同理 B 中所有元素也模 g 同余,于是 g 整除 bi – b1、a1 - aj,从而整除 s。另外,由于 C 中的每个元素都等于 D 中的 3 个元素之和,因此也有 s 整除 g。于是 s = g,这样就把求 g 的问题转化成了求 s。s 当然很好求,因为 D 的大小不会超过 200,000。我们终于搞定。
回顾一下解题的思路,你会发现整个过程虽然有点长,但其实每一步走过来都很自然。尽管有两个地方可能会卡一下,一是对 ki 不能为负的处理,二是求 C 中所有元素的最大公约数,但是认真思考一下很容易就能跨过去。本文到此为止,希望你能有所收获。