Наиболее эффективный алгоритм для нахождения этой суммы LCM

Проблема: найти

введите здесь описание изображения

Диапазон n : 1‹= n ‹= введите здесь описание изображения

Основная проблема заключается в обработке запросов (Q), которые могут быть большими. 1 ‹= Q ‹= введите здесь описание изображения

Методы, которые я использовал до сих пор:

Грубая сила

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

Сложность: введите здесь описание изображения

Предварительная обработка и обработка запросов в введите здесь описание изображения

Сначала я создаю таблицу, которая содержит значение функции Эйлера для каждого N. Это можно сделать за 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 . Я могу разложить число на простые множители за O(LogN), используя таблицу lp(наименьшее простое число), построенную выше, но я не могу понять, как получить ответ с помощью факторизации.


person Sarvagya    schedule 09.11.2015    source источник


Ответы (1)


Можно попробовать следующий алгоритм:

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

Теперь нужно найти сумму чисел n / gcd(i, n).

Давайте n = p1^i1 * p2^i2 * p3^j3, где число p1, 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
person Alexander Kuznetsov    schedule 09.11.2015
comment
Я тоже думал об этом. Максимальное количество простых чисел здесь может быть 8 ( as 2*3*5*7*11*13*17*19<=10^7 ), но не удалось преобразовать это в код. Как мне сгенерировать все j's (j1,j2,j3 etc) здесь? 8 вложенных петель?! Не могли бы вы добавить сюда псевдокод, пожалуйста. - person Sarvagya; 09.11.2015
comment
Вы передаете только 2 аргумента в рекурсивном вызове. Также for pj in keys of m что здесь м? - person Sarvagya; 09.11.2015
comment
Также найдите небольшую ошибку: начальное значение для mult2 должно быть 0, а не n. - person Alexander Kuznetsov; 09.11.2015
comment
Я не могу понять, в чем проблема, но этот код не дает желаемого результата. - person Sarvagya; 09.11.2015
comment
Я обновляю решения, у меня была ошибка в функции Эйлера totient, а также в функции рекурсии была ошибка, которую мы должны снова посетить в посещенном состоянии. Я запускаю это решение и совпадает с ответом грубой силы. Эта задача не кажется легкой, как на первый взгляд. - person Alexander Kuznetsov; 09.11.2015
comment
if(current in visited): есть идеи реализовать это на С++? - person Sarvagya; 09.11.2015
comment
Извините, я вас не понял. Кроме того, то, что вы по существу делаете, это генерация факторов из простой факторизации, а затем нахождение их вклада в ответ. Я не понимаю, почему это будет быстрее, чем просто поиск факторов (в O (logN)) и предварительная обработка O (N). - person Sarvagya; 09.11.2015
comment
Создайте функцию, которая возвращает текущий уникальный ключ, например. строка как контакт через запятую все значения из «текущей» карты. - person Alexander Kuznetsov; 09.11.2015
comment
Давайте продолжим обсуждение в чате. - person Alexander Kuznetsov; 09.11.2015