这是时间O(R log R) 和空间O(R) 的解决方案,其中R 是退役(退出)球员的数量。如果退役球员的 ID 是按升序排列的,那么您的最后一个见解是正确的:您可以读取 ID 并将它们冒泡到O(R) 时间和O(1) 内存中。当N 远大于R(例如数十亿)时,这会有所帮助,因为这排除了存储任何大小为N 的数组。
从概念上讲,退役球员 ID 是树上的叶子。这是N = 8 的树。我从所有 ID 中减去 1,因为这被证明是一个黑客问题,黑客喜欢从 0 开始计数。:-)
0-7
/ \
0-3 4-7
/ \ / \
0-1 2-3 4-5 6-7
/ \ / \ / \ / \
0 1 2 3 4 5 6 7
我们的想法是查看输入中的紧凑范围并计算出它们产生了多少字节。例如,范围 [0-3] 产生一个轮空:左括号(子树)中没有比赛,从 [4-7] 范围进入决赛的人将在决赛中轮空。范围 [4-7] 也是如此。基本上,如果一个范围覆盖一棵完整的子树,那就是再见。请注意,我们寻找最大的完整子树,例如[0-3] 而不是 [0-1] + [2-3] 分开。
[0-4] 呢?我们需要将范围分成 [0-3] 和 [4-4]。然后 [4-4] 是第二个子树(只有一个节点的普通子树),这意味着玩家 5 也将获得再见。所以 [0-4] 算作两个字节。我们通过计算数字 5(范围的大小)中的位来确定这一点。由于 5 = 1012,我们得到答案 2。这是 bit hack 部分,我将略过,但如果需要可以扩展。
最后一个要考虑的问题是限制范围大小。设N=1024 并考虑范围[4-100]。从 4 开始,子树在 7 处填满,此时我们应该处理范围 [4-7](并得到 1 bye),然后从 8 继续(该子树将依次在 15 处填充,依此类推)。计算右端也涉及到一些技巧。考虑起点 40=1010002。子树的大小由最低有效位给出,即 10002=8,所以我们应该在范围 [40-47] 之后中断。同样,我将掩盖细节,但如果需要可以扩展。
C 代码很短(很抱歉没有写 Java,已经有一段时间了)。为简洁起见,它使用 GCC 的 built-in popcount function 来计算位数,但有 many other methods 可用。同样,限制范围大小涉及finding the rightmost set bit。
#include <stdio.h>
void startRange(unsigned p, unsigned* start, unsigned* end, unsigned* limit) {
*start = *end = p;
*limit = p + (p & -p) - 1;
printf("started range %u limit %u\n", p, *limit);
}
int processRange(unsigned start, unsigned end) {
printf("processing range [%u,%u]\n", start, end);
return __builtin_popcount(end - start + 1);
}
int main() {
unsigned n, r, p, result;
unsigned start, end, limit;
/* read from standard input */
scanf("%d%d%d", &n, &r, &p);
startRange(p - 1, &start, &end, &limit);
result = 0;
while (--r) { /* read r-1 more numbers */
scanf("%d", &p);
p--;
if (p == end + 1 && p <= limit) {
/* continue while we have consecutive numbers, but not past the limit */
end++;
} else {
/* close the previous range and start a new one at p */
result += processRange(start, end);
startRange(p, &start, &end, &limit);
}
}
/* close any outstanding range we have */
result += processRange(start, end);
printf("%d\n", result);
return 0;
}