找到最小 N 使得 N! 的算法能被素数的幂整除

发布于 2024-11-15 06:10:53 字数 800 浏览 2 评论 0原文

是否有一种有效的算法来计算最小整数 N 使得 N!可被 p^k 整除,其中 p 是一个相对较小的素数,k 是一个非常大的整数。换句话说,

factorial(N) mod p^k == 0

如果给定 N 和 p,我想找出 p 能除以 N 的次数!,我会使用众所周知的公式,

k = Sum(floor(N/p^i) for i=1,2,...

我已经对 k 的小值进行了强力搜索,但这种方法非常失败随着 k 的增加,速度很快,而且似乎没有一种模式可以推断出更大的值。

编辑于 6/13/2011

根据 Fiver 和 Hammar 提出的建议,我使用准二进制搜索来解决问题,但并不完全按照他们建议的方式。使用上面第二个公式的截断版本,我将 N 的上限计算为 k 和 p 的乘积(仅使用第一项)。我使用 1 作为下限。使用经典的二分搜索算法,我计算了这两个值之间的中点,并使用该中点值作为第二个公式中的 N 来计算 k 的值,这次使用了所有项。

如果计算出的 k 太小,我会调整下限并重复。太大了,我首先测试一下在 midpoint-1 计算的 k 是否小于所需的 k。如果是这样,则返回中点作为最接近的 N。否则,我调整高点并重复。

如果计算出的 k 相等,我测试 midpoint-1 处的值是否等于 midpoint 处的值。如果是这样,我将高点调整为中点并重复。如果 midpoint-1 小于所需的 k,则返回中点作为所需的答案。

即使 k 值非常大(10 位或更多数字),这种方法也能以 O(n log(n)) 的速度运行。

Is there an efficient algorithm to compute the smallest integer N such that N! is divisible by p^k where p is a relatively small prime number and k, a very large integer. In other words,

factorial(N) mod p^k == 0

If, given N and p, I wanted to find how many times p divides into N!, I would use the well-known formula

k = Sum(floor(N/p^i) for i=1,2,...

I've done brute force searches for small values of k but that approach breaks down very quickly as k increases and there doesn't appear to be a pattern that I can extrapolate to larger values.

Edited 6/13/2011

Using suggestions proposed by Fiver and Hammar, I used a quasi-binary search to solve the problem but not quite in the manner they suggested. Using a truncated version of the second formula above, I computed an upper bound on N as the product of k and p (using just the first term). I used 1 as the lower bound. Using the classic binary search algorithm, I computed the midpoint between these two values and calculated what k would be using this midpoint value as N in the second formula, this time with all the terms being used.

If the computed k was too small, I adjusted the lower bound and repeated. Too big, I first tested to see if k computed at midpoint-1 was smaller than the desired k. If so, midpoint was returned as the closest N. Otherwise, I adjusted the highpoint and repeated.

If the computed k were equal, I tested whether the value at midpoint-1 was equal to the value at midpoint. If so, I adjusted the highpoint to be the midpoint and repeated. If midpoint-1 was less than the desired k, the midpoint was returned as the desired answer.

Even with very large values for k (10 or more digits), this approach works O(n log(n)) speeds.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

寒尘 2024-11-22 06:10:53

好吧,这很有趣。

定义 f(i) = (p^i - 1) / (p - 1)

将 k 写成一种有趣的“基数”,其中位置 i 的值就是这个 f(i)。

您可以从最高有效数字到最低有效数字执行此操作。首先,找到满足 f(j) <= k 的最大 j。然后计算 k / f(j) 的商和余数。将商存储为 q_j,将余数存储为 r。现在计算 r / f(j-1) 的商和余数。将商存储为 q_{j-1},将余数再次存储为 r。现在计算 r / f(j-2) 的商和余数。等等。

这会生成序列 q_j, q_{j-1}, q_{j-2}, ..., q_1。 (请注意,序列以 1 结尾,而不是 0。)然后计算 q_j*p^j + q_{j-1}*p^(j-1) + ... q_1*p。这就是您的 N。

示例:k = 9,p = 3。因此 f(i) = (3^i - 1) / 2。f(1) = 1,f(2) = 4,f(3) = 13因此,f(j) <= 9 时最大的 j 是 i = 2,且 f(2) = 4。取 9 / 4 的商和余数。这是 2 的商(即2 位置上的数字)和 1 的余数。

对于 1 的余数,找到 1 / f(1) 的商和余数。商为 1,余数为零,这样我们就完成了。

所以q_2 = 2,q_1 = 1。2*3^2 + 1*3^1 = 21,这是正确的N。

我在纸上有一个解释为什么这样有效,但我不知道如何用它来表达它文本...请注意,f(i) 回答了问题“(p^i) 中有 p 有多少个因子!”。一旦你找到了最大的 i,j 使得 j*f(i) 小于 k,并且意识到你真正在做的是找到小于 N 的最大的 j*p^i,剩下的就变得不那么重要了。例如,在我们的 p=3 示例中,我们得到 1-9 的乘积贡献的 4 个 p,10-18 的乘积贡献的另外 4 个 p,以及 21 的乘积贡献的另外一个。前两个只是 p^ 的倍数2; f(2) = 4 告诉我们,p^2 的每个倍数都会为乘积贡献 4 个以上的 p。

[更新]

代码总是有助于澄清。将以下 perl 脚本保存为 foo.pl 并以 foo.pl

运行它。请注意,** 是 Perl 的幂运算符,bdiv 计算 BigInts(无限精度整数)的商和余数,use bigint 告诉 Perl到处使用 BigInt。

#!/usr/bin/env perl

use warnings;
use strict;
use bigint;

@ARGV == 2
    or die "Usage: $0 <p> <k>\n";

my ($p, $k) = map { Math::BigInt->new($_) } @ARGV;

sub f {
    my $i = shift;
    return ($p ** $i - 1) / ($p - 1);
}

my $j = 0;
while (f($j) <= $k) {
    $j++;
}
$j--;

my $N = 0;

my $r = $k;
while ($r > 0) {
    my $val = f($j);
    my ($q, $new_r) = $r->bdiv($val);
    $N += $q * ($p ** $j);
    $r = $new_r;
    $j--;
}

print "Result: $N\n";

exit 0;

OK this is kind of fun.

Define f(i) = (p^i - 1) / (p - 1)

Write k in a kind of funny "base" where the value of position i is this f(i).

You do this from most-significant to least-significant digit. So first, find the largest j such that f(j) <= k. Then compute the quotient and remainder of k / f(j). Store the quotient as q_j and the remainder as r. Now compute the quotient and remainder of r / f(j-1). Store the quotient as q_{j-1} and the remainder as r again. Now compute the quotient and remainder of r / f(j-2). And so on.

This generates a sequence q_j, q_{j-1}, q_{j-2}, ..., q_1. (Note that the sequence ends at 1, not 0.) Then compute q_j*p^j + q_{j-1}*p^(j-1) + ... q_1*p. That's your N.

Example: k = 9, p = 3. So f(i) = (3^i - 1) / 2. f(1) = 1, f(2) = 4, f(3) = 13. So the largest j with f(j) <= 9 is i = 2 with f(2) = 4. Take the quotient and remainder of 9 / 4. That's a quotient of 2 (which is the digit in our 2's place) and remainder of 1.

For that remainder of 1, find the quotient and remainder of 1 / f(1). Quotient is 1, remainder is zero, so we are done.

So q_2 = 2, q_1 = 1. 2*3^2 + 1*3^1 = 21, which is the right N.

I have an explanation on paper for why this works, but I am not sure how to communicate it in text... Note that f(i) answers the question, "how many factors of p are there in (p^i)!". Once you find the largest i,j such that j*f(i) is less than k, and realize what you are really doing is finding the largest j*p^i less than N, the rest kind of falls out of the wash. In our p=3 example, for instance, we get 4 p's contributed by the product of 1-9, 4 more contributed by the product of 10-18, and one more contributed by 21. Those first two are just multiples of p^2; f(2) = 4 is telling us that each multiple of p^2 contributes 4 more p's to the product.

[update]

Code always helps to clarify. Save the following perl script as foo.pl and run it as foo.pl <p> <k>. Note that ** is Perl's exponentiation operator, bdiv computes a quotient and remainder for BigInts (unlimited-precision integers), and use bigint tells Perl to use BigInts everywhere.

#!/usr/bin/env perl

use warnings;
use strict;
use bigint;

@ARGV == 2
    or die "Usage: $0 <p> <k>\n";

my ($p, $k) = map { Math::BigInt->new($_) } @ARGV;

sub f {
    my $i = shift;
    return ($p ** $i - 1) / ($p - 1);
}

my $j = 0;
while (f($j) <= $k) {
    $j++;
}
$j--;

my $N = 0;

my $r = $k;
while ($r > 0) {
    my $val = f($j);
    my ($q, $new_r) = $r->bdiv($val);
    $N += $q * ($p ** $j);
    $r = $new_r;
    $j--;
}

print "Result: $N\n";

exit 0;
耳根太软 2024-11-22 06:10:53

使用您提到的公式,给定固定 pN = 1,2...k 值序列是非递减的。这意味着您可以使用二分搜索的变体来查找给定所需 kN

  • N = 1开始,计算k
  • 双倍 N 直到 k 大于或等于您想要的 k 以获得上限。
  • 对剩余间隔进行二分搜索以找到您的 k

Using the formula you mentioned, the sequence of k values given fixed p and N = 1,2... is non-decreasing. This means you can use a variant of binary search to find N given the desired k.

  • Start with N = 1, and calculate k.
  • Double N until k is greater or equal than your desired k to get an upper bound.
  • Do a binary search on the remaining interval to find your k.
救赎№ 2024-11-22 06:10:53

为什么不尝试使用您提到的第二个公式进行二分搜索来寻找答案?

您只需要考虑 N 的值,其中 p 除以 N,因为如果不是,则为 N!和(N-1)!除以 p 的同次幂,因此 N 不可能是最小的。

Why don't you try binary search for the answer, using the second formula you mentioned?

You only need to consider values for N, for which p divides N, because if it doesn't, then N! and (N-1)! are divided by the same power of p, so N can't be the smallest one.

萌无敌 2024-11-22 06:10:53

考虑

I = (pn)!

并忽略 p 以外的素因数。结果看起来像

I = pn * pn-1 * pn-2 * ... * p * 1
I = pn + (n-1) + (n-2) + ... 2 + 1
I = p(n2 +n)/2

所以我们试图找到最小的 n 使得

(n2 +n) /2 >= k

如果我还记得二次方程的话,就会得到

N = pn,其中 n >= (sqrt(1+8k) -1)/2

(PS有谁知道如何在markdown中显示偏旁符号?)

编辑:

这是错误的。让我看看是否可以挽救它......

Consider

I = (pn)!

and ignore prime factors other than p. The result looks like

I = pn * pn-1 * pn-2 * ... * p * 1
I = pn + (n-1) + (n-2) + ... 2 + 1
I = p(n2 +n)/2

So we're trying to find the smallest n such that

(n2 +n)/2 >= k

which if I remember the quadratic equation right gives us

N = pn, where n >= (sqrt(1+8k) -1)/2

(P.S. Does anyone know how to show the radical symbol in markdown?)

EDIT:

This is wrong. Let me see if I can salvage it...

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文