如何更快地解决项目euler#21?

时间:2011-11-13 19:56:39

标签: ruby

原始问题

  

设d(n)定义为n的适当除数之和(小于n的数均匀分为n)。   如果d(a)= b且d(b)= a,其中a为b,则a和b为友好对,a和b各自称为友好数字。

     

例如,220的适当除数是1,2,4,5,10,11,20,22,44,55和110;因此d(220)= 284. 284的适当除数是1,2,4,71和142;所以d(284)= 220。

     

评估10000以下所有友好数字的总和。

我通过生成1到10000之间的所有数字和它们对应的除数之和(即hash [220] = 284)的散列来解决问题。然后我将散列中的项目与散列的副本进行比较......无论如何,它都有效,但需要很长时间。我怎样才能让它更快?

def proper_divs_sum num
  divs = [1]
  for i in 2..((num/2) + 1)
    if num % i == 0
      divs.push i
    end
  end

  divs_sum = 0
  divs.each do |div|
    divs_sum += div
  end
  return divs_sum
end

def n_d_hash_gen num
  nd_hash = {}
  for i in 1..num
    nd_hash[i] = proper_divs_sum(i)
  end
  return nd_hash
end

def amicables num
  amicable_list = []
  hash1 = n_d_hash_gen(num)
  hash2 = n_d_hash_gen(num)

  hash1.each do |item1|
    hash2.each do |item2|
      if item1 != item2 && (item1[0] == item2[1] && item2[0] == item1[1])
        amicable_list.push item1
      end
    end
  end
  return amicable_list
end

另外,我是Ruby的新手,所以任何有关如何使其更像Ruby的提示也会非常感激。

6 个答案:

答案 0 :(得分:6)

函数 d(n)(通常称为σ(n))是divisor function的变体,它具有重要的属性这可以让你更有效地计算它。它是multiplicative function,这意味着如果 n = ab ,其中 a b 是互质的,那么 d (n)= d(a)d(b)

这意味着如果你可以计算 d(p k ,其中 p 是素数,那么 d(n)= d(p 1 k 1 )... d(p r k r ,其中 n = p 1 k 1 ... p < sub> r k r n 的主要因子分解。事实上,事实证明 d(p k )=(p k + 1 - 1)/(p - 1),所以 d(n) i (p i k i +1 - 1)/(p i - 1)。

因此,要为所有1≤n≤10000有效地计算 d(n),您可以使用筛子来计算所有 n <的主要因子分解/ em>,然后使用上面的公式使用素数因子分析计算 d(n)

完成后,您只需要一个简单的循环来计算 d(d(n)) = n 的总和>名词的。

通过将筛分步骤与 d(n)的计算相结合,甚至可以进一步优化,但我会将其作为感兴趣的练习。这个特定问题的大小没有必要。

答案 1 :(得分:2)

您可以采取以下措施来改进算法:

1)计算除数时无需循环到n / 2。停在sqrt(2)而不是。到那时你已经找到了一半的除数;另一半计算为n除以前半部分。

2)当你在哈希表中输入一个数字时,你可以立即检查它的友好双胞胎是否已经在哈希表中。不需要两个哈希表,也不需要两个比较它们的嵌套循环。

答案 2 :(得分:2)

分析您的方法

你正在采取的方法是从分裂开始,找到它的除数,总结它们并存储它们。你会注意到你用来找到除数的方法是天真的 - 我不是说这是一种侮辱;它只是说你的方法不使用它可能提供的任何信息,只会尝试每个数字来查看它是否为除数。它通过使用模块划分来实现这一点,并且几乎在所有情况下,大多数候选人都未通过测试。

更有建设性的东西

考虑一下你是否从未尝试过像这样的测试失败的数字。事实上,从除数开始并从那里积累红利就完全避开了这个问题。

你可以通过循环遍历每个数字&lt; = 5000来实现这一点。这些是你的除数,其倍数是你的红利。然后将除数加到每个倍数的除数之和。

这种方法逐位处理总和;当你通过每个除数时,你将有一个数组映射红利与除数。从那里,您可以使用您已经拥有的方法在此列表中搜索友好数字。

答案 3 :(得分:1)

分工是一个缓慢的过程。在你的方法中你做了很多,因此你的程序很慢。

首先,在尝试找到一个数字的所有除数时,你正在尝试所有除数不超过该数字的一半作为潜在除数。你可以通过不超过数字的平方根来改进。如果一个数字可以被大于它的平方根的数字整除,则除法的结果将小于平方根。这将消除一些不必要的分歧。

此外,如果一个数字不能被2分割,它也将不能被4,6,8等分割。最好除以素数并建立可能的除数。

然而,问题可以通过根本不进行任何划分来解决。

答案 4 :(得分:0)

你可以“欺骗”并使用Ruby的stdlib素数:http://rbjl.net/37/euler-021.rb

答案 5 :(得分:0)

Another solution in Java:

static int sum_Of_Divisors(int n){
    int limit = n;
    int sum = 0;
    for(int i=1;i<limit;i++){
        if(n%i==0){
            if(i!=1)
                sum += (i + n/i);
            else 
                sum += i;
            limit = n/i;
        }
    }       
    return sum;
}

static boolean isAmicable(int n, HashSet<Integer> set){
    int sum = sum_Of_Divisors(n);   
    if(sum_Of_Divisors(sum)==n && n!=sum){
        set.add(sum);
        return true;
    }
    return false;
}

static long q21(){
    long sum = 0;
    HashSet<Integer> set = new HashSet<Integer>();
    for(int i=1;i<10000;i++){
        if(!set.contains(i)){
            if(isAmicable(i,set)){
                set.add(i);
            }
        }
    }
    for(Integer i: set) sum+=i;
    return sum;
}