找到此LCM求和的最有效算法

时间:2015-11-09 13:27:46

标签: c++ algorithm number-theory sieve lcm

问题:查找

enter image description here

n 的范围:1< = n< = enter image description here

主要挑战是处理可能很大的查询(Q)。 1< = Q< = enter image description here

到目前为止我使用的方法:

暴力

while(Q--)
{
    int N;
    cin>>N;
    for(int i=1;i<=N;i++)
        ans += lcm(i,N)/i ;
}

复杂性:enter image description here

预处理和处理enter image description here

中的查询

首先,我构建一个表,其中包含每个N的euler totient函数的值。 这可以在O(N)中完成。

void sieve()
{
    // phi table holds euler totient function value
    // lp holds the lowest prime factor for a number
    // pr is a vector which contains prime numbers 
    phi[1]=1;
    for(int i=2;i<=MAX;i++)
    {
        if(lp[i]==0)
        {
            lp[i]=i;
            phi[i]=i-1;
            pr.push_back(i);
        }
        else
        {
            if(lp[i]==lp[i/lp[i]])
                phi[i] = phi[i/lp[i]]*lp[i];
            else phi[i] = phi[i/lp[i]]*(lp[i]-1);
        }
    for(int j=0;j<(int)pr.size()&&pr[j]<=lp[i]&&i*pr[j]<=MAX;j++)
        lp[i*pr[j]] = pr[j];
}

对于每个查询,分解N并将d*phi[d]添加到结果中。

for(int i=1;i*i<=n;i++)
{
   if(n%i==0)
   {
    // i is a factor
    sum += (n/i)*phi[n/i];
    if(i*i!=n)
        {
            // n/i is a factor too
            sum += i*phi[i];
        }
   }
}

这需要O(sqrt(N))。

复杂性:O(Q * sqrt(N))

处理O(1)

中的查询

对于上面描述的筛选方法,我添加了一个在O(NLogN)中计算我们需要的答案的部分

for(int i=1;i<=MAX;++i)
{
    //MAX is 10^7
    for(int j=i;j<=MAX;j+=i)
    {
        ans[j] += i*phi[i];
    }
}

不幸的是,这超出了给定的约束和时间限制(1秒)。

我认为这涉及到关于N的素因子分解的一些聪明的想法。 我可以使用上面构建的lp(最低素数)表来对O(LogN)中的数字进行分解,但我无法弄清楚如何使用因子分解得出答案。

1 个答案:

答案 0 :(得分:0)

您可以尝试以下算法:

lcm(i,n) / i  = i * n / i * gcd(i, n) = n / gcd(i, n)

现在应该找到数字n / gcd(i, n)的总和。

n = p1^i1 * p2^i2 * p3^j3p1, p2, ... pk为素数。

n / gdc(i, n)gcd(i , n) == 1的项目数phi[n] = n*(p1-1)*(p2-1)*...*(pk-1)/(p1*p2*...*pk),因此添加到总和n*phi[n]

n / gdc(i, n)gcd(i , n) == p1的项目数phi[n/p1] = (n/p1)*(p1-1)*(p2-1)*...*(pk-1)/(p1*p2*...*pk),因此添加到总和n/p1*phi[n/p1]

n / gdc(i, n)gcd(i , n) == p1*p2的项目数phi[n/(p1*p2)] = (n/(p1*p2))*(p1-1)*(p2-1)*...*(pk-1)/(p1*p2*...*pk),因此添加到总和n/(p1*p2)*phi[n/(p1*p2)]

现在回答是总和

n/(p1^j1*p2^j2*...*pk^jk)  phi[n/(p1^j1*p2^j2*...*pk^jk)]

全部

j1=0,...,i1  
j2=0,...,i2
....  
jk=0,...,ik

此总和中的项目总数为i1*i2*...*ik,远小于O(n)。

要计算此总和,您可以使用具有自由参数初始编号,当前表示和初始表示的递归函数:

initial = {p1:i1, p2:i2, ... ,pn:in} 
current = {p1:i1, p2:i2, ... ,pn:in} 
visited = {}

int calc(n, initial, current, visited):
   if(current in visited):
      return 0
   visited add current 
   int sum = 0
   for pj in keys of current:
      if current[pj] == 0:
        continue
      current[pj]--
      sum += calc(n, initial, current)
      current[pj]++  

   mult1 = n 
   for pj in keys of current:
      mult1 /= pj^current[pj]

   mult2 = mult1
   for pj in keys of current:
      if initial[pj] == current[pj]:
         continue
      mult2 = mult2*(pj -1)/pj

   sum += milt1 * mult2
   return sum