【问题标题】:Compute factorials efficiently有效地计算阶乘
【发布时间】:2019-06-16 05:21:22
【问题描述】:

我有一个 N 最多 10^5 的数组。 我需要用给定的LR 解决Q 范围查询。 Q

对于每个查询,我需要在 L 到 R 范围内找到不同的元素 然后找到它们的阶乘。

例如,如果数组是{5, 4, 2, 4, 5, 5, 7}

如果L = 2R = 4

那么我们有{ 4 : 2 times, 2: 1 time },所以答案是(2!)*(1!) = 2

如果L = 1R = 5 然后我们有{5 : 2 times, 4: 2 times, 2 : 1 time},所以答案是(2!)*(2!).(1!) = 4

O((N)*(Q)) 这个问题的解决方案是显而易见的。 我该如何优化它。

注意:所有阶乘均以 1000000007 为模计算

【问题讨论】:

  • 10^5 是数组的最大 size 还是最大元素 value?
  • @paxdiablo 10^5 是数组的最大大小。元素最多可达 10^9
  • "所有阶乘均以 1000000007 为模计算" 这是编程比赛吗?如果是这样,请发布一个链接。如果比赛仍在进行中,请不要问和回答有关它的问题

标签: c++ algorithm data-structures combinations


【解决方案1】:

好的,因为(根据您的评论),数组中只能有 105 个元素,这也是数组中每个可能值的最大 count .

因此,您可以做的一件事是将所有这些阶乘预先计算到一个数组中并使用它们进行计算。

可能最好的方法是编写一个元程序,将值构造成 C++ 结构,然后您可以将其包含在自己的代码中(a)。结构看起来像:

unsigned int facMod10p7[] = {
    0,
    1,
    2,
    :
    /* whatever (10^5)! % 1000000007 is */
};

一旦你有了它,每个阶乘的查找是 O(1)。要进行查询方面的工作,您只需遍历数组(从 LR)计算唯一值的数量。

这可能最好用map<unsigned int, unsigned int> 来完成,第一个字段是值(假设这里是无符号值,但您可以很容易地使它有符号),第二个字段是它发生的次数。

对于{4, 2, 4}L2/R4 情况,您最终会得到一张地图:

{ [2] = 1, [4] = 2 }

然后,迭代查找每个计数的阶乘并获取所有计数的乘积是一件简单的事情。

由于它是 O(n) 循环中的 O(1) 查找/乘法,因此产生的复杂度将为 O(n)。


(a) 例如,输出前 10,000 个阶乘的 Python 程序在我的盒子上大约需要 30 秒才能生成整个表(在我的 WSL 环境中,不一定以它的致盲而闻名/O 速度,至少要等到即将发布的下一个版本):

real 0m29.137s
user 0m28.438s
sys  0m0.547s

如果你想做自己的测试,代码是:

print('static unsigned int facMod10p7[] = {\n    0,')
val = 1
mult = 2
for i in range(100000):
    print('    {},'.format(val) % 1000000007)
    val *= mult
    mult += 1
print(');')

【讨论】:

  • 数组中的查询怎么样,请看例子。查询的答案是一个范围内不同元素计数的阶乘乘积。
  • (10^5)!有456574位数字。哎呀。
  • @ggorlen: (10^5)! % 1000000007 最多有十位数字 :-) 在任何情况下,这些都只需要计算一次并将结果结构包含在您的代码中.例如,使用 MPIR 是轻而易举的事。
  • @alex,您提出的复杂性中的二次项来自哪里?您用 O(n) 计算范围内的元素,阶乘查找和乘法现在是 O(1)。这是 O(n) 的结果。
  • @n.m.,您可能应该将该评论指向问题而不是答案。如果问题在某种程度上是无效的,它应该被删除(带着答案)但是人们(嗯,至少我)只会按要求回答问题,因为除了质量本身之外,我对它的有效性几乎没有假设当然。
【解决方案2】:

让我们看看你的例子:

n = 7
A[n] = {5, 4, 2, 4, 5, 5, 7}
L = 2
R = 4
p = 1000000007
  1. 计算A[] 的直方图H[],即O(n)

    大小为n 的数组中的所有值都在L,R 之间,其中m 是您的任何值的最大计数。

     H[2] = 1
     H[3] = 0
     H[4] = 2
    

    在代码中类似于:

    int H[R-L+1],i;
    for (i=L,i<=R;i++) H[i-L]=0;
    for (i=0,i<n;i++)
     if ((A[i]>=L)&&(A[i]<=R)
      H[i-L]++;
    

    我将H 索引移动了 L,所以我们不会浪费空间,所以:

     H[0] = 1
     H[1] = 0
     H[2] = 2
    
  2. 找到m = max(H[]),即O(R-L+1)

    简单地说:

    int m;
    for (m=H[0],i=L+1;i<=R;i++)
     if (m<H[i-L])
      m=H[i-L];
    

    所以:

    m = 2
    
  3. 预计算直到 m 的所有阶乘,即 O(m)

    int F[m+1],j;
    j=1; F[0]=j;
    for (i=1;i<=m;i++)
     {
     j=modmul(j,i,p); // j = j*i mod p
     F[i]=j;
     }
    

    所以:

    F[] = { 0!,1!,2! }
    F[] = { 0 ,1 ,2  }
    
  4. 计算阶乘的最终 PI,即 O(R-L+1)

    这么简单:

    for (j=1,i=L;i<=R;i++)
     j=modmul(j,F[H[i-L]],p);
    // here j is your result
    

    所以:

    j = F[H[0]]*F[H[1]]*F[H[2]] 
    j = F[1]*F[0]*F[2] 
    j = 1!*0!*2! 
    j = 2 
    

如你所见,整个过程是O(n+m+R-L),比你的O(N*Q)好多了

如果您执行此操作的次数超过您应该考虑将 F[] 预计算到您的最大 n 值...

如果我选择L=1,R=5,那么内容是这样的:

//      1 2 3 4 5
H[] = { 0,1,0,2,3 }
m = 3
//      0 1 2 3
F[] = { 1,1,2,6 }
PI(F[H]) = F[0]*F[1]*F[0]*F[2]*F[3]
         =   1 *  1 *  1 *  2! * 3! 
         =   2*6
         =  12

你的直方图中有错误,因为 5 在数组中是 3 次而不是 2 !!!但是,如果将范围应用于数组的索引而不是它的值,那么这不是一个错误,我的方法需要对索引进行轻微更改......所有i for 循环将从L 改为R .

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-17
    • 1970-01-01
    • 2023-03-22
    • 2015-04-19
    • 1970-01-01
    • 2014-09-23
    相关资源
    最近更新 更多