【问题标题】:Factorial of integer mod m fast calculation [duplicate]整数mod m的阶乘快速计算[重复]
【发布时间】:2015-04-15 12:18:58
【问题描述】:

是否可以在不循环整个表达式链的情况下计算 Factorial(x) mod m

((1 % m) * (2 %m) * (3 % m) * ... (x % m)) % m?

更准确地说,m 可以是 1 m x : 1x

【问题讨论】:

  • 你必须遍历整个链。如果不是,那就不是阶乘!
  • x 比 m 大还是小?
  • 我怀疑你能做到。我的观点是您执行循环的效率极低。除了最后一个之外,你所有的模运算都不做任何事情。
  • 如果您想要 O(m^0.5+epsilon) 而不是 Mysticial 答案的不断改进,请参阅 stackoverflow.com/questions/9727962/…,尤其是 ohad 和 Fredrik Johansson 的答案。
  • @thang 我想你很困惑。这种计算 x 的方法! mod m 不需要 m 是素数。鼓舞人心的例子是能够使用威尔逊定理实现素性检验。仅当数字为素数时才进行素数测试是没有意义的。我引用的答案中没有使用威尔逊定理。当模数不是素数时,快速傅里叶变换起作用。

标签: algorithm


【解决方案1】:

Factorial 的快速算法很少

  • 所以答案是:是的,您无需遍历所有值即可计算阶乘
  • 我看到的都是素数分解(包括我的算法)
  • 因此,只需使用模乘法而不是普通乘法即可
  • 看这里:Fast exact bigint factorial 是我的快速算法
  • 另一个答案还包含指向摆动素数算法的链接...

[备注]

  • 对于N!,您需要一个素数列表,直到N
  • 但其余代码可以处理能够容纳N,m的算术
  • 所以不需要庞大的数字...

[edit1] 我的 32 位 C++ 实现

//---------------------------------------------------------------------------
DWORD modmul(DWORD a,DWORD b,DWORD n)
    {
    DWORD _a,_b,_n;
    _a=a;
    _b=b;
    _n=n;
    asm {
        mov eax,_a
        mov ebx,_b
        mul ebx     // H(edx),L(eax) = eax * ebx
        mov ebx,_n
        div ebx     // eax = H(edx),L(eax) / ebx
        mov _a,edx  // edx = H(edx),L(eax) % ebx
        }
    return _a;
    }
//---------------------------------------------------------------------------
DWORD modfact0(DWORD n,DWORD m)         // (n!) mod m (naive approach)
    {
    DWORD i,f;
    for (f=1,i=2;i<=n;i++) f=modmul(f,i,m);
    return f;
    }
//---------------------------------------------------------------------------
DWORD modfact1(DWORD n,DWORD m)         // (n!) mod m (mine fast approach)
    {
    if (n<=4)
        {
        if (n==4) return 24;
        if (n==3) return  6;
        if (n==2) return  2;
        if (n==1) return  1;
        if (n==0) return  1;
        }
    int N4,N2,p,i,j,e; DWORD c,pp;
    N4=(n>>2)<<2;
    N2=N4>>1;
    c=modfact1(N2,m); c=modmul(c,c,m);  // c=((2N)!)^2;
    for (i=0;;i++)                      // c*= T2
        {
        p=primes_i32.dat[i];
        if (!p) break;
        if (p>N4) break;
        for (e=0,j=N4;j;e+=j&1,j/=p);
        if (e)                          // c*=p^e
            {
            if (p==2) c<<=e;
            else for (pp=p;;)
                {
                if (int(e&1)) c=modmul(c,pp,m);
                e>>=1; if (!e) break;
                pp=modmul(pp,pp,m);
                }
            }
        }
    for (i=N4+1;i<=n;i++) c=modmul(c,i,m);
    return c;
    }
//---------------------------------------------------------------------------

素数:

  • DWORD primes_i32.dat[] 是直到 n 的所有素数的预计算排序(升序)列表

结果如下:

[  18.529 ms] slow modfact0(1000000,1299721) = 195641
[   2.995 ms] fast modfact1(1000000,1299721) = 195641
[  96.242 ms] slow modfact0(5000000,9999991) = 2812527
[  13.305 ms] fast modfact1(5000000,9999991) = 2812527
  • 1299721 是我发现的第一个接近 1000000 的素数
  • 如果m 不是质数并且子结果为零,那么您可以忽略其余的乘法以大幅加快速度...

希望结果OK 没什么可比的……

【讨论】:

  • 你声称你的算法很快。它计算 n 的速度有多快!对于 n 大约 10^6?如果你需要生成一个最多 n 的素数列表,它们中大约有 n/log n,所以我看不出你会比 n/log n 做得更好,而其他算法在时间 sqrt(n) 上工作日志(n)^c。
  • @DouglasZare 我的方法已经有了可供使用的素数。如果您添加素数的计算,那么对于标准的 no bigint 值,您是对的,但是如果您尝试计算甚至 1000!,那么与阶乘本身相比,素数的计算时间非常小。因为即使是单次乘法也是在远离 O(1) 的 bigint 值上,这会将天真的方法从 O(N) 转换为更复杂的多项式,这非常依赖于实现。这就是为什么估计很难做到的原因......所以我只能粗略地估计操作(在那个答案的末尾)。
  • @DouglasZare 无论如何,这个问题是关于是否可以在少于 N 次迭代中计算 ModFactorial,所以答案是正确的。您不需要使用我的方法,如果您需要避免它们的计算,可以使用 Prime 表。对于模块化算术,可能有更多的优化方法......但是在不知道解决方案的约束(x,m 的范围)的情况下很难给出更多建议......
  • 您在我不相信的答案中说 O(log n)。与天真的方法或 O(x^(0.5+epsilon)) 的快速方法相比,您是否实现并测试了您的方法?
  • @DouglasZare of粗糙我做了,例如1000!采用快速方法32ms和天真101ms
猜你喜欢
  • 2010-12-17
  • 2012-04-01
  • 2018-09-17
  • 1970-01-01
  • 1970-01-01
  • 2023-03-22
  • 2011-09-03
  • 1970-01-01
  • 2016-06-02
相关资源
最近更新 更多