我有 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
比蛮力更好吗?
答案 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])
,其中i
从0
变为n
。由于所有p[i]
都至少为2,因此渐近地比蛮力解更好。
更好理解的一个例子:假设输入是(5,4),(5,1),(2,0)。 p_max
为lcm(5,5,2) - 1 = 10 - 1 = 9
,因此我们创建了包含10个元素的数组,最初用零填充。现在让我们一对一地进行:
remainders[1] = 1
和remainders[6] = 1
remainders[4] = 1
和remainders[9] = 1
remainders[0] = 1
,remainders[2] = 1
,remainders[4] = 1
,remainders[6] = 1
和remainders[8] = 1
。因此,数组中零值的第一个索引是3,这是一个理想的解决方案。