在不使用结构的情况下冒泡某种条件?
Posted
技术标签:
【中文标题】在不使用结构的情况下冒泡某种条件?【英文标题】:Bubbling up some sort of condition without using a structure? 【发布时间】:2019-02-21 07:28:02 【问题描述】:有一场比赛。玩家由整数标识,并以与他们相邻的整数的锦标赛风格进行比赛。所以,1 播放 2,3 播放 4,5 播放 6,7 播放 8。然后,1/2 播放 3/4,5/6 播放 7/8,然后 1/2/3/4 播放 5/6/ 7/8。
示例输入:
8 5
1 2 3 4 5
第一行 = 第一个数字是锦标赛中的玩家数量(总是一些 2^N 的数字),第二个数字是在锦标赛开始前退出的玩家数量
第二行=退出玩家的ID
如果玩家自动进入下一轮,因为他们本来要玩的人退出(称之为 BYE),增加计数。最后输出计数。
所以样本输入的输出将是 2(6 在第一轮自动晋级,因为 6 应该与 5 退出,最终在倒数第二轮,6/7/8 自动晋级) .
我正在考虑要么将所有内容都保存在树中(但这不会真的很有效吗?),或者只是在你去的时候进行解析,然后冒泡 BYE。第二种解决方案的问题是我不知道如何考虑/存储关系,即谁将扮演谁,以及在实施中你如何在没有结构的情况下冒泡。
【问题讨论】:
这里的任务是什么,创建整棵树还是只数再见? 计数再见。我只想打印正确的输出。 必须在不使用数据结构存储数据的情况下解决任务? 不,可以使用结构体。 N 可以有多大? 【参考方案1】:您可以使用一个简单的数组(大小为 2^N)。将您的参与者编码为 0 代表缺席,1 代表出席,并模拟比赛。在索引2*k
的每一轮中,玩家将与索引2*k + 1
对战,“获胜者”被移动到索引k
。再见条件是 XOR,“赢家”是 OR。在伪代码中,
while player_count > 1
for k in range (player_count / 2)
byes += arr[2*k] ^ arr[2*k + 1]
arr[k] = arr[2*k] | arr[2*k + 1]
player_count /= 2
空间和时间都与玩家数量呈线性关系。
【讨论】:
@J.Doe 零和一,根据玩家的存在。【参考方案2】:没有结构就不能存储任何东西,但是某些时间结构可以是隐式的,例如在递归的情况下,我们有没有明确声明的堆栈。
递归解法示例:
public class App
public static void main(String[] args)
// I use 0 based indexing here
Set<Integer> absent = new HashSet<>(Arrays.asList(0, 1, 2, 3, 4));
int count = rec(absent, 8, 0);
System.out.println("count = " + count);
private static int rec(Set<Integer> absent, int currentGroupSize, int start)
if (currentGroupSize == 1)
return absent.contains(start) ? -1 : 0;
int r1 = rec(absent, currentGroupSize / 2, start);
int r2 = rec(absent, currentGroupSize / 2, start + currentGroupSize / 2);
if (r1 == -1)
if (r2 == -1)
return -1;
else
return r2 + 1;
else
if (r2 == -1)
return r1 + 1;
else
return r1 + r2;
【讨论】:
我添加了示例。【参考方案3】:这是时间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;
【讨论】:
以上是关于在不使用结构的情况下冒泡某种条件?的主要内容,如果未能解决你的问题,请参考以下文章
如何在不使用 SQL 中的多个连接条件的情况下获取同一行中的所有值?