找到范围内的mod操作的总和

时间:2012-02-16 16:42:47

标签: algorithm math

如果我们有两个数字,比如说a and b那么我们如何才能找到sum of b%i where i ranges from 1 to a的值? 一种方法是迭代从1到a的所有值,但是有没有有效的方法? (优于O(n)?) 例如:如果a = 4且b = 5则需要ans = 5%1 + 5%2 + 5%3 + 5%4 = 4 感谢。

1 个答案:

答案 0 :(得分:4)

对于i > b,我们有b % i == b,因此可以在恒定时间内轻松计算总和的一部分((a-b)*b,如果a >= b,则为0)。

i <= b的部分仍有待计算(i == b给出0,因此可以忽略)。你可以用O(sqrt(b))步骤

来做到这一点
  • 对于i <= sqrt(b),请计算b % i并添加到总和
  • 对于i > sqrt(b),请k = floor(b/i),然后b % i == b - k*ik < sqrt(b)。因此,对于k = 1ceiling(sqrt(b))-1,请hi = floor(b/k)lo = floor(b/(k+1))。有hi - lo个数字ik*i <= b < (k+1)*ib % i的总和为sum_{ lo < i <= hi } (b - k*i) = (hi - lo)*b - k*(hi-lo)*(hi+lo+1)/2

如果a <= sqrt(b),则只应用第一个项目符号,停在a。如果sqrt(b) < a < b在第二个项目符号中从k = floor(b/a)运行到ceiling(sqrt(b))-1并将最小k的上限调整为a

总体复杂度O(min(a,sqrt(b)))。

代码(C):

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

unsigned long long usqrt(unsigned long long n);
unsigned long long modSum(unsigned long long a, unsigned long long b);

int main(int argc, char *argv[]){
    unsigned long long a, b;
    b = (argc > 1) ? strtoull(argv[argc-1],NULL,0) : 10000;
    a = (argc > 2) ? strtoull(argv[1],NULL,0) : b;
    printf("Sum of moduli %llu %% i for 1 <= i <= %llu: %llu\n",b,a,modSum(a,b));
    return EXIT_SUCCESS;
}

unsigned long long usqrt(unsigned long long n){
    unsigned long long r = (unsigned long long)sqrt(n);
    while(r*r > n) --r;
    while(r*(r+2) < n) ++r;
    return r;
}

unsigned long long modSum(unsigned long long a, unsigned long long b){
    if (a < 2 || b == 0){
        return 0;
    }
    unsigned long long sum = 0, i, l, u, r = usqrt(b);
    if (b < a){
        sum += (a-b)*b;
    }
    u = (a < r) ? a : r;
    for(i = 2; i <= u; ++i){
        sum += b%i;
    }
    if (r < a){
        u = (a < b) ? a : (b-1);
        i = b/u;
        l = b/(i+1);
        do{
            sum += (u-l)*b;
            sum -= i*(u-l)*(u+l+1)/2;
            ++i;
            u = l;
            l = b/(i+1);
        }while(u > r);
    }
    return sum;
}