所有适当除数的总和

时间:2013-09-16 19:59:36

标签: c performance

我已经解决了一个问题: 给定自然数n(1 <= n <= 500000),请输出所有适当除数的总和。

定义:自然数的适当除数是严格小于数的除数。

e.g。数字20有5个适当的除数:1,2,4,5,10,除数求和为:1 + 2 + 4 + 5 + 10 = 22.

输入

  

一个整数,表示测试用例的数量(大约等于200000),后面跟着很多行,每个行包含1到500000之间的一个整数。

输出

  

每行一个整数:分别给出整数的除数求和。

实施例

示例输入:

  

3
  2
  10个
  20

示例输出:

  

1
  8
  22

我的代码如下:

/* @BEGIN_OF_SOURCE_CODE */

#include <stdio.h>
#include <stdlib.h>

    int main(int argc, const char * argv[])
    {
        int sum = 0,
        cases = 0,
        i, j, buff;

        scanf("%d", &cases); //Number of tests


        int *n;
        n = (int*) malloc(cases * sizeof(int)); //Defining array for numbers to be tested///////

        for (i = 0; i < cases; i++) {
            scanf("%d", &n[i]);
        }
        for (i = 0; i < cases; i++ ) {
            buff = n[i] / 2;
            if (n[i] == 1) {
                sum = -1;
            }


            if (!(n[i] & 1)) {
                for (j = 2; j < buff; j++) {
                    if (n[i] % j == 0) {
                        sum += n[i] / j + j;
                        buff /= j;
                    }
                }
            }


            else {
                for (j = 3; j < buff; j += 2) {
                    if (n[i] % j == 0) {
                        if (n[i] / j == j) { sum += j; break; }
                        else sum += n[i] / j + j;
                    }
                    buff /= j;
                }
             }
            printf("%d\n", ++sum);
            sum = 0;
        }
        return 0;
    }
    /* @END_OF_SOURCE_CODE */

但速度不够快。有什么建议吗?

6 个答案:

答案 0 :(得分:7)

我已更新以下代码以更快终止。在MacBookPro6,1(2.66 GHz Intel Core i7)上运行1到500,000的所有整数,用Apple GCC 4.2.1和-O3编译。

它在Wikipedia page for the divisor function的属性部分中使用σ x n )的公式。使用预先计算的素数列表可以更快地完成。 (需要126个才能支持高达500,000的输入,这会将时间减少到不到四分之一秒。)还有一些可以消除的分歧,代价是稍微混乱代码。

//  Return the least power of a that does not divide x.
static unsigned int LeastPower(unsigned int a, unsigned int x)
{
    unsigned int b = a;
    while (x % b == 0)
        b *= a;
    return b;
}


//  Return the sum of the proper divisors of x.
static unsigned int SumDivisors(unsigned int x)
{
    unsigned int t = x;
    unsigned int result = 1;

    //  Handle two specially.
    {
        unsigned int p = LeastPower(2, t);
        result *= p-1;
        t /= p/2;
    }

    //  Handle odd factors.
    for (unsigned int i = 3; i*i <= t; i += 2)
    {
        unsigned int p = LeastPower(i, t);
        result *= (p-1) / (i-1);
        t /= p/i;
    }

    //  At this point, t must be one or prime.
    if (1 < t)
        result *= 1+t;

    return result - x;
}

答案 1 :(得分:2)

您不必分配空间。只是一行一行。 对于每一行,都有一个O(n ^ 1/2)算法。

#include <iostream>
using std::cout; using std::endl; using std::cin;

int main() {
   int count, number;
   cin >> count;
   for (int i = 0; i < count; ++i) {
      cin >> number;
      int sum = 1;
      for ( int j = 2; j * j <= number; ++j ) {
         if ( number % j == 0 ) {
            sum += j;
            sum += number / j;
         }
         if ( j * j == number ) sum -= j; // recalculate twice
      }
      cout << sum << endl;
   }
}

这是200,000测试用例的运行时

real    0m55.420s
user    0m0.016s
sys     0m16.124s

答案 2 :(得分:0)

我首先不将数字存储在数组中。您不需要 - 只需读取值,处理它,然后输出结果。编译器可能没有意识到n[i]在整个循环中是相同的值,并且没有其他任何修改它。

逻辑对我来说似乎不太清楚。 if (n[i] == 1) { sum = 1} else ...比设置sum = -1更有意义。

您也许也可以保留一个“常见因素”列表(http://en.wikipedia.org/wiki/Memoization),这样您就不必多次重新计算同样的事情。 [如果你知道somehing有24因子,那么例如它也有2个,3个,4个,6个和8个。

答案 3 :(得分:0)

I replied to a similar question on stackoverflow

有一种性能更快的算法,该算法基于formula for the sum of divisor使用素数因子中的分解。

首先构建一个primetable,使得最后的素数小于你的数字的上限。然后将公式应用于每个条目。如果数字写为

n = a1^p1 * a1^p2 *... *an^pn

找到给定数字n的总和的复杂性将是

p1+p2+...+pn = roughtly log(n)

优于早期停止循环的第一个优化的复杂度O(sqrt(n))

答案 4 :(得分:0)

假设您有办法相对快速地计算素数。这可能是一次性的前期活动,受最大输入值的平方根限制。在这种情况下,您已经知道最大输入值(500000)的界限,因此您只需将一个素数表硬编码到程序中即可。

static unsigned P[] = {
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71,
73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151,
157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233,
239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317,
331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419,
421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503,
509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607,
613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701
};

static int P_COUNT = sizeof(P)/sizeof(*P);

现在,从素数中,对于每个输入值,您可以:

  • 计算素数因子分解
  • 计算每个素数因子总和的乘积。

这将导致除数之和。从总和中减去输入值以获得适当除数的总和。这两个步骤可以组合成一个循环。

该算法有效,因为乘法多项式自然会导致多项式项的所有组合相乘的总和。在每个多项式项由分割输入的素数幂组成的情况下,乘以一起的项的组合构成除数。该算法速度很快,应该能够在Core i3或更好的处理器上在不到一秒的时间内在[1,500000]区间内处理500000个数字。

以下函数实现上述方法。

unsigned compute (unsigned n) {
    unsigned sum = 1;
    unsigned x = n;
    for (int i = 0; i < P_COUNT; ++i) {
        if (P[i] > x / P[i]) break;    /* remaining primes won't divide x */
        if (x % P[i] == 0) {           /* P[i] is a divisor of n */
            unsigned sub = P[i] + 1;   /* add in power of P[i] */
            x /= P[i];                 /* reduce x by P[i] */
            while (x % P[i] == 0) {    /* while P[i] still divides x */
                x /= P[i];             /* reduce x */
                sub = sub * P[i] + 1;  /* add by another power of P[i] */
            }
            sum *= sub;                /* product of sums */
        }
    }
    if (x > 1) sum *= x + 1;           /* if x > 1, then x is prime */
    return sum - n;
}

答案 5 :(得分:0)

此代码在O(n * log(n))中的复杂性。但是你可以用恒定的时间输出所需的答案。

int ans[500000 + 10], m = 500000;

int f(){
    for(int i = 1; i <= m; i++){
       for(int j = i + i; j <= m; j += i){
           ans[j] += i;
       }
     }
}

此处 ans 是一个数组,其中包含从2到m的适当除数之和。