数论算法。段上的大多数除数
Number theory algorithms. Most divisors on the segment
我正在寻找一种有效的算法来解决以下问题。令 d(n)
表示 n
的正因子数,其中 n
是正整数。我们得到了一些 1 <= a <= b <= 10^18
,任务是在段 [a..b]
上找到 d
的最大值,并且(可能这部分我们需要更复杂的算法)找到使值最大化的数字d
.
前段时间在free access中发现了如下代码:http://ideone.com/qvxPj
unsigned long long n, res;
int p, primes[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 51, 53, 59, 61, 67, 71};
unsigned long long mul(unsigned long long a, unsigned long long b){
unsigned long long res = 0;
while (b){
if (b & 1LL) res = (res + a);
if (res >= n) return 0;
a = (a << 1LL);
b >>= 1LL;
}
return res;
}
void backtrack(int i, int lim, unsigned long long val, unsigned long long r){
if (r > res) res = r;
if (i == p) return;
int d;
unsigned long long x = val;
for (d = 1; d <= lim; d++){
x = mul(x, primes[i]);
if (x == 0) return;
backtrack(i + 1, d, x, r * (d + 1));
}
}
int main(){
p = sizeof(primes) / sizeof(int);
while (scanf("%llu", &n) != EOF){
res = 0;
backtrack(0, 100, 1, 1);
printf("Maximum number of divisors of any number less than %llu = %llu\n", n, res);
}
return 0;
}
如果有人向我解释它是如何工作的,我将非常高兴,因为(对我而言)这个程序运行得非常快。
在此先感谢您的帮助。
它像这样遍历所有数字:
num = P1^D1 * P2^D2 * P3^D3 * ... * Ps^Ds
constraints:
Pi <= 71
1 <= Di <= 100
sequence (Pi) is a sorted list of first s primes
sequence (Di) is nonincreasing
num <= n
让我们检查第一个约束条件。假设最小最优数有质因数q > 71。如果这个数中没有使用任何素数 p <= 71 ,那么我们可以用 p[=55= 替换 q ] 同样的力量。显然,除数的数量将保持不变,但数量会减少 -> 矛盾。那么就没有小于71的未使用素数了。但是71以内的所有素数的乘积已经很大了,我们考虑的数肯定大于64位n。这不可能。
现在我们来解释第二个和第三个约束条件。假设我们的最小最优数在其因式分解中有一个质数 q,但没有一些质数 p,其中 p 。然后我们可以将 q 替换为 p 以相同的顺序,该数字将具有相同数量的除数,但会变得更少 -> 矛盾。这意味着在寻求最佳(最小)数的因式分解中的所有素数必须恰好是前 s 个素数。使用的素数集合中不能有空洞。
顺便说一句,Di <= 100 很明显,因为即使 2^100 也不适合 64 位整数。
现在我们要解释第四个约束。假设 D[i] < D[i+1] 对于某些 i。那么我们可以将 P[i]^D[i] * P[i+1]^D[i+1] 替换为 P[i]^D[ i+1] * P[i+1]^D[i],数字会变小。例如,将 5^2 * 7^3 替换为 5^3 * 7^2:除数相同,但结果较小。显然,如果我们搜索最小最优数,我们也可以安全地假设这个条件。
现在让我们考虑一下代码。
mul
是一个计算 a 和 b 的乘积的小函数。它是通过一个有趣的二进制程序计算出来的。这个过程的主要原因是:如果乘积大于n,那么函数returns0。这个过程只是为了防止可能发生的溢出。
终于到了backtrack
。这是一个通常的递归搜索。 val
是当前数,r
是它的约数,i
显示我们现在要添加的素数的索引,lim
限制每个素数的幂为100. 在一开始,您会看到当前最佳答案的更新(存储在 res
中)和硬停止条件(使用所有素数)。
然后有一个循环检查当前素数的每个幂。它从幂 1 开始,因为零幂是被禁止的。它在 x
中维护当前数字,并在每次迭代中将其乘以 Pi 以增加功率。如果 x
变得大于 n,它会立即停止。最后,它调用自己以寻找下一个素数。
作为对@stgatilov 回答的补充,我将证明选择将素数限制为小于或等于 71 的素数是合理的。
我使用稍微修改过的代码版本来记录具有最大除数的最大数字。对于 1000000000000000000 或 999999999999999999,我得到:
897612484786617600 = 28 * 34 * 52 * 72 * 11 * 13 * 17 * 19 * 23 * 29 * 31 * 37
总除数为 103680。
这意味着对于所有 18 位十进制数字,不涉及大于 37 的质数是找到具有最大除数的整数。
我正在寻找一种有效的算法来解决以下问题。令 d(n)
表示 n
的正因子数,其中 n
是正整数。我们得到了一些 1 <= a <= b <= 10^18
,任务是在段 [a..b]
上找到 d
的最大值,并且(可能这部分我们需要更复杂的算法)找到使值最大化的数字d
.
前段时间在free access中发现了如下代码:http://ideone.com/qvxPj
unsigned long long n, res;
int p, primes[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 51, 53, 59, 61, 67, 71};
unsigned long long mul(unsigned long long a, unsigned long long b){
unsigned long long res = 0;
while (b){
if (b & 1LL) res = (res + a);
if (res >= n) return 0;
a = (a << 1LL);
b >>= 1LL;
}
return res;
}
void backtrack(int i, int lim, unsigned long long val, unsigned long long r){
if (r > res) res = r;
if (i == p) return;
int d;
unsigned long long x = val;
for (d = 1; d <= lim; d++){
x = mul(x, primes[i]);
if (x == 0) return;
backtrack(i + 1, d, x, r * (d + 1));
}
}
int main(){
p = sizeof(primes) / sizeof(int);
while (scanf("%llu", &n) != EOF){
res = 0;
backtrack(0, 100, 1, 1);
printf("Maximum number of divisors of any number less than %llu = %llu\n", n, res);
}
return 0;
}
如果有人向我解释它是如何工作的,我将非常高兴,因为(对我而言)这个程序运行得非常快。
在此先感谢您的帮助。
它像这样遍历所有数字:
num = P1^D1 * P2^D2 * P3^D3 * ... * Ps^Ds
constraints:
Pi <= 71
1 <= Di <= 100
sequence (Pi) is a sorted list of first s primes
sequence (Di) is nonincreasing
num <= n
让我们检查第一个约束条件。假设最小最优数有质因数q > 71。如果这个数中没有使用任何素数 p <= 71 ,那么我们可以用 p[=55= 替换 q ] 同样的力量。显然,除数的数量将保持不变,但数量会减少 -> 矛盾。那么就没有小于71的未使用素数了。但是71以内的所有素数的乘积已经很大了,我们考虑的数肯定大于64位n。这不可能。
现在我们来解释第二个和第三个约束条件。假设我们的最小最优数在其因式分解中有一个质数 q,但没有一些质数 p,其中 p 。然后我们可以将 q 替换为 p 以相同的顺序,该数字将具有相同数量的除数,但会变得更少 -> 矛盾。这意味着在寻求最佳(最小)数的因式分解中的所有素数必须恰好是前 s 个素数。使用的素数集合中不能有空洞。
顺便说一句,Di <= 100 很明显,因为即使 2^100 也不适合 64 位整数。
现在我们要解释第四个约束。假设 D[i] < D[i+1] 对于某些 i。那么我们可以将 P[i]^D[i] * P[i+1]^D[i+1] 替换为 P[i]^D[ i+1] * P[i+1]^D[i],数字会变小。例如,将 5^2 * 7^3 替换为 5^3 * 7^2:除数相同,但结果较小。显然,如果我们搜索最小最优数,我们也可以安全地假设这个条件。
现在让我们考虑一下代码。
mul
是一个计算 a 和 b 的乘积的小函数。它是通过一个有趣的二进制程序计算出来的。这个过程的主要原因是:如果乘积大于n,那么函数returns0。这个过程只是为了防止可能发生的溢出。
终于到了backtrack
。这是一个通常的递归搜索。 val
是当前数,r
是它的约数,i
显示我们现在要添加的素数的索引,lim
限制每个素数的幂为100. 在一开始,您会看到当前最佳答案的更新(存储在 res
中)和硬停止条件(使用所有素数)。
然后有一个循环检查当前素数的每个幂。它从幂 1 开始,因为零幂是被禁止的。它在 x
中维护当前数字,并在每次迭代中将其乘以 Pi 以增加功率。如果 x
变得大于 n,它会立即停止。最后,它调用自己以寻找下一个素数。
作为对@stgatilov 回答的补充,我将证明选择将素数限制为小于或等于 71 的素数是合理的。
我使用稍微修改过的代码版本来记录具有最大除数的最大数字。对于 1000000000000000000 或 999999999999999999,我得到:
897612484786617600 = 28 * 34 * 52 * 72 * 11 * 13 * 17 * 19 * 23 * 29 * 31 * 37
总除数为 103680。
这意味着对于所有 18 位十进制数字,不涉及大于 37 的质数是找到具有最大除数的整数。