【问题标题】:Minimum common remainder of division除法的最小公余数
【发布时间】:2018-01-15 17:09:06
【问题描述】:

我有 n 对数字: ( p[1], s[1] ), ( p[2], s[2] ), ... , ( p[n] , s[n] )

其中 p[i] 是大于 1 的整数; s[i] 是整数:0

有没有办法确定最小正整数 a ,这样对于每一对:

( s[i] + a ) mod p[i] != 0

还有什么比蛮力更好的吗?

【问题讨论】:

  • 不应该是 0
  • 提醒一下,PPCG(Programming Puzzles & Code Golf)并不是 SO(与 Math.SE 和 MO 不同)更容易的网站,它举办编程竞赛。曾有人提议将该网站重命名为 Programming Contests & Code Golf,但 SE 并不关心 PPCG...

标签: algorithm numbers


【解决方案1】:

有可能比蛮力做得更好。蛮力将是 O(A·n),其中 A 是我们正在寻找的 a 的最小有效值。

下面描述的方法使用min-heap 并实现O(n·log(n) + A·log(n)) 时间复杂度。

首先,请注意将 a 替换为 (p[i] - s[i]) + k * p[i] 形式的值会导致对于任何正整数 k,第 ith 对中的提醒值为零。因此,该表单的数字是无效的 a 值(我们正在寻找的解决方案与所有这些都不同)。

所提出的算法是一种有效的方法来生成该形式的数字(对于所有 ik),无效值对于a按递增顺序。只要当前值与前一个值相差超过 1,就意味着中间有一个有效的 a

下面的伪代码详细说明了这种方法。

1. construct a min-heap from all the following pairs (p[i] - s[i], p[i]), 
    where the heap comparator is based on the first element of the pairs.
2. a0 = -1; maxA = lcm(p[i])
3. Repeat
     3a. Retrieve and remove the root of the heap, (a, p[i]).
     3b. If a - a0 > 1 then the result is a0 + 1. Exit.
     3c. if a is at least maxA, then no solution exists. Exit.
     3d. Insert into the heap the value (a + p[i], p[i]).
     3e. a0 = a

备注:这样的a可能不存在。如果在 LCM(p[1], p[2], ... p[n]) 下没有找到有效的a,则保证没有有效的a 存在。


我将在下面展示该算法如何工作的示例

考虑以下 (p, s) 对:{ (2, 1), (5, 3) }。

第一对表示 a 应该避免像 1, 3, 5, 7, ... 这样的值,而第二对表示我们应该避免像2, 7, 12, 17, ... .

最小堆最初包含每个序列的第一个元素(伪代码的第 1 步)——下面以粗体显示:

  • 1, 3, 5, 7, ...

  • 2, 7, 12, 17, ...

我们检索并删除堆的头部,,两个粗体中的最小值,这是1。我们将该序列中的下一个元素添加到堆中,因此堆现在包含元素 2 和 3:

  • 1, 3, 5, 7, ...

  • 2, 7, 12, 17, ...

我们再次检索堆的头部,这次它包含值 2,并将该序列的下一个元素添加到堆中:

  • 1, 3, 5, 7, ...

  • 2, 7, 12, 17, ...

算法继续,接下来我们将检索值 3,并将 5 添加到堆中:

  • 1, 3, 5, 7, ...

  • 2, 7, 12, 17, ...

最后,现在我们检索值 5。此时我们意识到值 4 不在 a 的无效值中,因此这就是我们正在寻找的解决方案。

【讨论】:

  • a0 怎么样?看起来它是不变的,永远不会改变。有必要吗?
  • 必须是一些额外的a0 分配可能
  • 确实,我错过了一项任务。 a0 是目前已知无效的最大值。
  • 顺便说一句,“(p[i] - s[i]) + k * p[i] 形式的值导致提醒为零”是什么意思?通过什么提醒部门? ( (p[i] - s[i]) + k * p[i] ) mod p[i] == p[i] - s[i],不为零(如果 s[i] != p[i])
  • @obratim 从某种意义上说,如果我们将 a 替换为该值,我们将得到一个等于 0 的提醒。
【解决方案2】:

我可以想到两种不同的解决方案。第一:

p_max = lcm (p[0],p[1],...,p[n]) - 1;
for a = 0 to p_max:
    zero_found = false;
    for i = 0 to n:
        if ( s[i] + a ) mod p[i] == 0:
            zero_found = true;
            break;
    if !zero_found:
        return a;
return -1;

我想这就是你所说的“蛮力”。请注意,p_max 表示p[i]s - 1 的最小公倍数(解要么在闭区间[0, p_max] 内,要么不存在)。在最坏的情况下,这个解决方案的复杂性是O(n * p_max)(加上计算 lcm 的运行时间!)。关于时间复杂度有一个更好的解决方案,但它使用了一个额外的二进制数组 - 经典的时空权衡。它的想法类似于埃拉托色尼筛法,但用于余数而不是素数:)

p_max = lcm (p[0],p[1],...,p[n]) - 1;
int remainders[p_max + 1] = {0};
for i = 0 to n:
    int rem = s[i] - p[i];
    while rem >= -p_max:
        remainders[-rem] = 1;
        rem -= p[i];
for i = 0 to n:
    if !remainders[i]:
         return i;
return -1;

算法说明:首先,我们创建一个数组remainders,它将指示整个集合中是否存在某个负余数。什么是负余数?很简单,注意 6 = 2 mod 4 等价于 6 = -2 mod 4。如果remainders[i] == 1,这意味着如果我们将i 添加到s[j] 之一,我们将得到p[j](即是 0,这就是我们想要避免的)。数组填充了所有可能的负余数,最多 -p_max。现在我们所要做的就是搜索第一个i,这样remainder[i] == 0 并返回它,如果它存在 - 请注意解决方案不必存在。在问题文本中,您指出您正在搜索最小 positive 整数,我不明白为什么不适合零(如果所有 s[i] 都是正数)。但是,如果这是一个强要求,只需将 for 循环更改为从 1 而不是 0 开始,然后递增 p_max。 该算法的复杂度为n + sum (p_max / p[i]) = n + p_max * sum (1 / p[i]),其中i0 变为n。由于所有p[i]s 至少为 2,这比暴力解法渐进地好。

一个更好理解的例子:假设输入是 (5,4), (5,1), (2,0)。 p_maxlcm(5,5,2) - 1 = 10 - 1 = 9,所以我们创建了一个包含 10 个元素的数组,最初用零填充。现在让我们逐对进行:

  • 从第一对开始,我们有 remainders[1] = 1remainders[6] = 1
  • 第二对给出remainders[4] = 1remainders[9] = 1
  • 最后一对给出remainders[0] = 1remainders[2] = 1remainders[4] = 1remainders[6] = 1remainders[8] = 1

因此,数组中第一个零值的索引是 3,这是一个理想的解决方案。

【讨论】:

  • 请注意,a 的最小值没有必要低于 max(p) -- 考虑以下示例: (p, s) = { (2 , 1), (5, 1), (5, 3) } -- 在这种情况下,最小有效 a 是 6。
  • @qwertyman 在这种情况下,我的算法将输出 0 作为解决方案,我已经评论了特殊情况(在任何系统模 n 中,0 始终被视为常规余数,所以我看不到为什么a 应该 > 0) 。如果a 不能是负数,而是正数,那么你是对的——我们应该将循环扩展到 max(p)。有没有一个例子,其中一些s[i] 是零,我们仍然需要超过 max(p)?
  • 是的,例如如果 (p, s) 对的集合是 { (2, 1), (3, 0), (5, 1), (5, 3) },那么从 0 到 7 的值不起作用,a = 8 是最小的有效值。
  • @qwertyman 很好的例子!实际上,p_max 应该是 lcm(p[i]) - 1,这是可能的最高值(或者解决方案不存在),编辑了我的答案。我现在检查了您的解决方案,它真的很优雅 - 赞成!请注意,在堆上生成新数字的停止条件也是当您达到 lcm(p[i]) 时,因此不幸的是,无论哪种方式都无法避免计算 lcm。
  • 感谢您的赞赏和考虑我的建议 - 也赞成!事实上,lcm 在这两种解决方案中都是必需的。
猜你喜欢
  • 2016-11-19
  • 1970-01-01
  • 2011-09-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-05
  • 2020-07-11
相关资源
最近更新 更多