Оптимизация вычисления делителей

Created on: December 28, 2024

Answered using O1 by Chat01

Question

Есть задача: определим функцию f(x), равную количеству делителей числа x. По заданным двум целым числам a и b надо вычислить f(a) + f(a+1) + ... + f(b).
На вход подаётся два целых числа a и b (1 <= a <= b <= 2^31 - 1). Напиши решение этой задачи на c++ и подробно его объясни. Стоит помнить, что решение должно быть оптимальным с ассимптотической точки зрения

Thought

Ищу оптимальное решение

Я разбираюсь с программным заданием, где даны два числа a и b, и нужно найти сумму количества делителей чисел от a до b. В случае большого диапазона значений это задача приводит к необходимости применить асимптотически оптимальный подход.

Определяю сумму

Использую формулу sum_{d=1}^N floor(N/d) с итерацией от 1 до sqrt(N), добавляя floor(N/k), чтобы найти стандартное число делителей.

Вычисляю и корректирую

Сначала нахожу сумму всех делителей чисел от 1 до b. Затем убираю все делители от 1 до a-1, получая результат в O(sqrt(N)).

Создание функции

Разрабатываю функцию sum_divisors(N), которая вычисляет сумму делителей, используя 64-битные целые числа для хранения больших сумм, что позволяет эффективно обрабатывать большие значения N.

Думал для 5 секунд

Answer

Чтобы решить задачу эффективно, необходимо воспользоваться свойствами делителей чисел и оптимизировать вычисления, избегая прямого перебора всех чисел и их делителей в диапазоне [a,b][a, b]. Прямая реализация, которая вычисляет количество делителей для каждого числа в диапазоне, будет слишком медленной из-за большого размера возможного диапазона (1ab23111 \leq a \leq b \leq 2^{31} - 1).

Шаги решения

  1. Понимание задачи: Нам нужно вычислить сумму количества делителей для каждого числа в диапазоне от aa до bb, то есть:

    S=f(a)+f(a+1)++f(b)S = f(a) + f(a+1) + \dots + f(b)

    где f(x)f(x) — количество делителей числа xx.

  2. Математическое преобразование: Известно, что сумма количества делителей для чисел от 11 до NN равна:

    x=1Nf(x)=d=1NNd\sum_{x=1}^{N} f(x) = \sum_{d=1}^{N} \left\lfloor \frac{N}{d} \right\rfloor

    Это преобразование позволяет вычислить сумму делителей за более эффективное время.

  3. Оптимизация вычислений: Для большого NN (до 23112^{31} - 1) прямой перебор всех делителей будет слишком медленным. Вместо этого воспользуемся свойством, что количество уникальных значений Nd\left\lfloor \frac{N}{d} \right\rfloor ограничено порядка 2N2 \sqrt{N}. Это позволяет вычислить сумму за O(N)O(\sqrt{N}) времени.

  4. Вычисление суммы в диапазоне [a,b][a, b]: Для получения суммы в заданном диапазоне, нужно вычислить:

    S(a,b)=x=abf(x)=x=1bf(x)x=1a1f(x)S(a, b) = \sum_{x=a}^{b} f(x) = \sum_{x=1}^{b} f(x) - \sum_{x=1}^{a-1} f(x)

    То есть, разность между суммой делителей от 11 до bb и от 11 до a1a-1.

Реализация на C++

Ниже представлен код на C++, который реализует описанный подход:

cpp
#include <bits/stdc++.h> using namespace std; typedef long long ll; // Функция для вычисления суммы количества делителей от 1 до N ll sumDivisors(ll N) { ll res = 0; ll i = 1; // Суммируем floor(N / i) для i от 1 до sqrt(N) while (i * i <= N) { res += N / i; i++; } // Теперь обрабатываем оставшиеся значения floor(N / d) // где d < i, что эквивалентно k = floor(N / d) > sqrt(N) ll last = N / i; for(ll k = 1; k <= last; ++k){ // d_min = floor(N / (k + 1)) + 1 // d_max = floor(N / k) ll d_min = N / (k + 1) + 1; ll d_max = N / k; if(d_max < i) break; // уже обработали d_min = max(d_min, (ll)i); res += k * (d_max - d_min + 1); } return res; } int main(){ ll a, b; // Чтение входных данных cin >> a >> b; // Вычисляем сумму делителей от 1 до b и от 1 до (a-1) ll sum_b = sumDivisors(b); ll sum_a_minus_1 = (a > 1) ? sumDivisors(a - 1) : 0; // Результат - разность двух сумм ll result = sum_b - sum_a_minus_1; cout << result; return 0; }

Пояснение кода

  1. Функция sumDivisors(ll N):

    • Цель: Вычислить сумму количества делителей для всех чисел от 11 до NN.
    • Метод:
      • Первый цикл: Проходим по всем ii от 11 до N\sqrt{N}, добавляя Ni\left\lfloor \frac{N}{i} \right\rfloor к результату. Это охватывает все делители, где dNd \leq \sqrt{N}.
      • Второй цикл: Обрабатываем оставшиеся значения Nd\left\lfloor \frac{N}{d} \right\rfloor, где d>Nd > \sqrt{N}. Здесь k=Ndk = \left\lfloor \frac{N}{d} \right\rfloor принимает значения от 11 до Ni\left\lfloor \frac{N}{i} \right\rfloor, где ii — текущее значение после первого цикла.
      • Оптимизация: Используя свойства деления, избегаем повторного перебора и учитываем только уникальные значения kk, уменьшая количество итераций до порядка 2N2 \sqrt{N}.
  2. Функция main():

    • Чтение входных данных: Считывает два числа aa и bb.
    • Вычисление суммы делителей:
      • Вычисляет сумму делителей от 11 до bb с помощью sumDivisors(b).
      • Вычисляет сумму делителей от 11 до a1a-1 с помощью sumDivisors(a - 1), если a>1a > 1.
    • Результат: Разность двух сумм дает искомую сумму делителей в диапазоне [a,b][a, b].
    • Вывод результата: Печатает полученную сумму.

Обоснование эффективности

  • Временная сложность: O(N)O(\sqrt{N}) для каждого вызова sumDivisors. Так как NN может быть до 23112^{31} - 1, N\sqrt{N} составляет примерно 4634046\,340, что достаточно быстро даже для большого количества запросов.
  • Пространственная сложность: O(1)O(1), так как используется постоянное количество памяти независимо от входного размера.

Таким образом, данное решение оптимально с точки зрения асимптотической сложности и эффективно справляется с заданными ограничениями.

Share this Q&A