如何在将结果返回给 Mod M 的同时找到其数的阶乘总和最大的列表

问题描述

给定不同的数字列表,比如说;

list1 --> [2,4,2]
list2 --> [3,5]
list3 --> [4,4],list4 --> [5,5]
list5 --> [6]

求每个列表中数字的阶乘之和。 返回和的最大值。将总和计算为 Mod M,因为它们可能非常大。

例如,在上面的列表中,每个列表的总和,取 M=10^9 + 7,是

list1 --> (2!+4!+2!)%M = (2+24+2)%M = 26
list2 --> (3!+5!)%M =  (6+120)%M = 126
list3 --> (4!+4!+4!+4!+4!+4!)%M = (24+24+24+24+24+24)%M = 144
list4 --> (5!+5!)%M = (120+120)%M = 240
list5 --> (6!)%M = 720

从上面的计算可以看出,list5为必填列表。

我的问题是,当每个列表中的项目变大时,阶乘变得非常快,以至于 Mod 开始生效。 Mod生效后,我将无法找到最大数量,例如1000000008 % M = 1,but 10 % M = 10。 如何有效地找到实际获得最大总和的列表?

约束

--> Size of each list is between 1(inclusive) and N(exclusive)
--> Each list contain numbers between 2(inclusive) and N(exclusive)
--> N is between 2(inclusive) and 10^6 (exclusive)

解决方法

在所有数组中找到最大元素,其中包含最大计数的元素。说这是p,重复r次。

所以这个数组的总和至少是 r * p!。

现在,假设其他数组的最大元素为 q,长度为 m。所以最大可能的阶乘总和是 m * q!。这个另一个数组可能有更大的总和需要什么? IE。值得检查吗?

如果 q = p 那么我们只需要 m > r 就值得检查了。

如果 q = p-1 那么我们需要 m * (p-1)! > r * p * (p-1)!所以 m > r * p。

如果 q = p-2 那么我们需要 m * (p-2)! > r * p * (p-1) * (p-2)!所以 m > r * p * (p-1)

等等...如果q = p-k,那么我们需要m > r * p! / (p-k)!

这是否有用取决于数组中的值。例如。如果单个数组的最大值为 N 重复一次,并且任何数组中的下一个最大值为 N-1 或更低,则具有 N 的数组必须是(或绑定)最大和数组(因为它需要N (N-1) 等于 N!)。

这有助于减少要检查的数组。除非所有数组中的 max 元素相对于 N 非常小,否则这可能会将您缩小到幸存者中的一组非常小的(最大值,最大值重复)值。例如。如果 p > sqrt(N) 那么幸存者可以有一个不小于 p-1 的最大值。

例如,如果 N 是 100 并且所有列表中的最大值是 11。假设这是一个只有一个元素的列表:[11]。嗯,11! = 39,916,800。自100*9!只是 36,288,000,我们可以丢弃最大元素

我们可以继续递归地使用这个想法来找到单个最高值列表。假设我们只有两个最大值:p 和 p-1。对于所有具有最大值 p 的列表,将它们的 ps 计数更改为零,并且对于每个去掉的 p,给出 p (p-1)s。接下来,找到具有最低计数 (p-1s) 的幸存列表,并从所有幸存列表中删除计数。然后重复这个过程。

对于相对于 N 非常小的值,我们将获得超过 2 个可能的最大值。您可以将其扩展到 3+,或者可能只是直接计算幸存者的阶乘总和,因为所有值都会很小。

---- 这是一个 O(num_lists * N) 版本----

假设有 L 个列表。

1. Sort each list in O(N) using bucket sort,total is O(L * N).
2. Find the max element across lists,use repetition to break ties. 
3. Say this is p,repeated r times.
4. For each list (including this one): call remove(p,r,list)
5. Repeat for the new max element.

def remove(p,list):
    while r > 0 and list.has(p):
        list.remove(p)
        r -= 1
    if r > 0:
        list.remove((p-1) * r * p)

如果列表出现无法解决的情况,remove 方法可以提前停止(如本答案的顶部部分)。

这总共是 O(L * N)。每次我们调用 remove 时,我们要么删除最多 N 个元素中的一个,要么将我们删除的内容减少最多 N 个元素中的 1 个,因此在耗尽列表之前每个列表最多可以调用 O(2N) 次。

---- Ruby 代码 ----

def find_max_fsum(lists,n)
  sorted_lists = Hash.new { |h,k| h[k] = [0] * (n+1) }
  
  lists.each_with_index do |list,i|
    list.each do |elt|
      sorted_lists[i][elt] += 1
    end
  end

  max_val = n + 1
  
  while max_val > 1 do
    max_val -= 1
    max_val_reps = 0
    sorted_lists.values.each do |sorted_list|
      max_val_reps = [max_val_reps,sorted_list[max_val]].max
    end
    if max_val_reps > 0
      sorted_lists.each do |i,sorted_list|
        success = remove(max_val,max_val_reps,sorted_list)
        if !success
          sorted_lists.delete(i)
        end
      end
    end
  end

  sorted_lists.keys.each do |i|
    print "max_fsum list: #{lists[i].to_s}\n"
  end
end

def remove(max_val,sorted_list)
  if max_val_reps <= sorted_list[max_val]
    sorted_list[max_val] -= max_val_reps
    return true
  end
  if (max_val_reps > sorted_list[max_val]) && max_val > 1 
    max_val_reps -= sorted_list[max_val]
    sorted_list[max_val] = 0
    return remove(max_val - 1,max_val_reps * max_val,sorted_list)
  end
  
  return false
end

find_max_fsum([[2,4,2],[3,5],[6,3,3],[4,4],[5,4]],6)
max_fsum list: [6,3]

find_max_fsum([[2,4]
,

每个正整数都有一个唯一的“基本阶乘”表示。该表示看起来像:a_1 * 1! + a_2 + 2! + a_3 * 3! + ... 与每个 a_n <= n。这类似于以 10 为底的阶乘,但随着您在列表中向下移动,基数会增加。

如果你有一个非规范形式的阶乘表示,你可以通过从 1、2、3 等开始来解决这个问题,对于每个取 a_n mod (n+1) 作为余数,并调整下学期了。这与取 25 * 1 + 3 * 10 + 40 * 100 之类的东西并找出它是 5 * 1 + 5 * 10 + 4 * 1000 并因此在基数 10 中它是 4055 没有什么不同。

从您的列表中,您可以轻松生成阶乘表示,然后对其进行规范化。具有最大规范表示的那个就是您的答案。

这是您展示其工作原理的示例:

list1 --> [2,2]
    2 * 2! + 1 * 4!
list2 --> [3,5]
    1 * 3! + 1 * 5!
list3 --> [4,4]
    6 * 4! = 1 * 4! + 1 * 5! (This is the first one we had to canonicalize.)
list4 --> [5,5]
    2 * 5!
list5 --> [6]
    6!

现在您只需对规范表示进行排序并发现 list5 最大,然后是 list4,然后是 list3,然后是 list2,然后是 list1。>