最小共同余数除法

时间:2018-01-15 17:09:06

标签: algorithm numbers

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

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

有没有办法确定最小正整数 a ,例如每对:

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

比蛮力更好吗?

2 个答案:

答案 0 :(得分:2)

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

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

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

建议的算法是生成该表单编号的有效方法(对于所有 i k ),无效值 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 的无效值,因此这是我们正在寻找的解决方案。

答案 1 :(得分: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的运行时间!)。关于时间复杂度有一个更好的解决方案,但它使用了额外的二进制数组 - 经典的时空权衡。它的想法类似于Eratosthenes的Sieve,但是对于剩余的而不是素数:)

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并返回它(如果存在的话) - 注意解决方案不一定存在。在问题文本中,您已指出您正在搜索最小整数,我不明白为什么零不适合(如果所有s[i]都为正)。但是,如果这是一个强烈的要求,只需将for循环更改为从1开始而不是0,并增加p_max。 此算法的复杂性为n + sum (p_max / p[i]) = n + p_max * sum (1 / p[i]),其中i0变为n。由于所有p[i]都至少为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,这是一个理想的解决方案。