A:用时 3 分钟
tag: 暴力
题目描述:给你一个正整数 n,每次操作你可以将一个数变成两个相等的数,其值等于 n / 4 (向下取整),若某个数小于 4 则不可操作,问最多可以产生多少个数。
思路:想象一棵存储整数节点的树,根节点为 n,每次操作会产生两个相等节点。显然这棵树始终是完全二叉树,而当前数的个数就等于同一层所有节点的个数,因此我们只要不断地将 n 除以 4,同时维护节点个数,直到叶子节点为止。
代码:

——————
B:用时 22 分钟
tag: 数学(数论)
题目描述:给你一个数 dd...dddd (n! 个 d),判断它能否被 1、3、5、7 和 9 整除。
思路:
判断 1 - 任何数都可以被 1 整除。
判断 5 - 只有 d = 5 才可以被整除。
判断 3 - 一个数可以被 3 整除,当且仅当其数位之和可以被 3 整除,而这里数位之和等于 n! * d,所以等价于判断 n! 和 d 中的 3 的因子的个数,不难得到整除条件为 n >= 3 或者 3 | d。
这里有个小问题,如何证明 “一个数可以被 3 整除,当且仅当其数位之和可以被 3 整除”?
我们可以把任何整数写成 a_0 + 10a_1 + 100a_2 + ... + 10^na_n 的形式,并将其变换为 (a_0 + a_1 + a_2 + ... + a_n) + 3^2 * (a_1 + a_2 + ... + a_n),因为右边的部分必定被 3 整除,所以任何整数模 3 的结果就等于其数位之和模 3 的结果,……,证毕。
判断 9 - 一个数可以被 9 整除,当且仅当其数位之和可以被 9 整除,证明方式同上,不难得到整除条件为 n >= 6 或者 3 <= n < 6 且 d % 3 == 0 或者 n < 3 且 d = 9
判断 7 - 这是本题的难点。首先,我们把 dd..ddd(n! 个 d) 写成 d * 111..11(n! 个 1),所以 d = 7 时可以被 7 整除。如果 d != 7 呢?需要判断 111..11(n! 个 1)是否能被 7 整除。
考虑构造 111..11 的过程:0 -> 1 -> 11 -> 111 -> ... -> 111111,递推方程为:
x <- (x * 10 + 1)
因为我们关注的是模 7 的结果,所以给两边加上取模:
x mod 7 <- (x * 10 + 1) mod 7
不妨设 x mod 7 = r,下一个余数为 r1,则上式等价于:
r1 <- ((r * 3) mod 7 + 1) mod 7
利用这个递推式,我们可以写出余数的前几项:
0 -> 1 -> 4 -> 6 -> 5 -> 2 -> 0 -> 1 -> 4 -> 6 -> ...
发现周期!可知整除条件为 6 | n!,也就是 n >= 3。
为什么会有周期?如何证明周期存在?如果存在,那么周期是多少?
这些问题的答案正是线性同余发生器(LCG)的原理,具体可参照 Hull & Dobell 的论文。
即便你不知道什么是 LCG,你也大概率用过它 —— C语言的 rand() 函数就是一个 LCG。
代码:

——————
C:用时 N/A (一个小时没做出来)
tag:(优雅的)暴力
题目描述:给你一个整数数组,其中最多只有一个元素不是 1 或 -1,按升序输出所有子数组求和的结果(重复的值只需要输出一次)。空数组也算子数组,其求和结果为 0.
思路:显然我们并不能暴力枚举所有的子数组再求和,因为这样做的时间复杂度是 O(n^2)。换句话说,这道题肯定存在某个特殊条件,让我们不需要暴力枚举这么多的子数组。这个特殊条件就是元素的值域。
因为最多只有一个元素不是 1 或 -1,不妨先考虑所有元素都是 1 或 -1 的情况。
然后我想,这样一来子数组的和的取值范围就在 -n 到 n 了,而 n ~ 2e5,因此可以直接枚举 -n 到 n,再检查这个值是否存在……但无论怎么想,我也找不到快速判断存在性的方法。整个算法最终还是会不可避免地变成 O(n^2)。于是就卡在了这里。
遂看题解,只看了第一段,就恍然大悟。
究竟该如何利用 “1”?
“1” 代表 “连续变化”。
对于一个只由 1、-1 组成的数组,如果我们固定左端点,枚举右端点,计算出所有的子数组和,记最小的和为 m,最大的和为 M,则所有的子数组和一定能取到 m, m+1, m+2, ..., M 的每一个数,形成了线段 [m, M],且 0 一定位于线段内(空数组)。
想要计算出所有可能的子数组和,我们就需要枚举所有的左端点(或右端点),然后找到最小的子数组和以及最大的子数组和,得到 n 个线段,最后将这些线段合并,就得到了子数组和覆盖的范围。因为这些线段一定包含 0,所以它们都是相交的,只需要简单的 min max 就能处理合并的逻辑。
接下来考虑不是 1 或 -1 的 “特殊元素”。对于包含特殊元素的子数组,如果我们 “去掉” 这个特殊元素,则其子数组和的变化规律就如上述分析所述。现在加上这个特殊元素 x,可以理解为整体加上了 x 的偏移量,所以处理的方式和前面类似。
代码:

——————
B 和 C 都非常有趣,从中学到了很多。