Матч 16, Делимость (Divisibility)

 

Задан массив чисел a. Найти количество чисел от L до R включительно, которые делятся хотя бы на одно число массива a.

 

Класс: Divisibility

Метод: int numDivisible(int L, int R, vector <int> a)

Ограничения: a содержит от 1 до 18 элементов, 1 £ a[i], L, R £ 109.

 

Вход. Два целых числа L, R и массив целых чисел a.

 

Выход. Количество чисел от L до R включительно, которые делятся хотя бы на одно число массива a.

 

Пример входа

L

R

a

293

784

{1}

255

734

{2}

1

1000000000

{2,3}

 

Пример выхода

492

240

666666667

 

 

РЕШЕНИЕ

комбинаторика, принцип включения - исключения

 

Воспользуемся фактом, что

numDivisible(L, R, a) = numDivisible(1, R, a) – numDivisible(1, L - 1, a).

Значение numDivisible(1, n, a) будем вычислять при помощи функции f(n, a). Рассмотрим процесс вычисления результата в зависимости от количества элементов в массиве a.

1. Если a содержит один элемент, то ответом будет значение n / a[0] (округление до целого производится вниз).

2. Пусть a содержит два элемента. Ответ будет меньше n / a[0] + n / a[1], так как будут существовать числа, которые делятся на a[0] и на a[1] одновременно. И в приведенной сумме такие числа будут считаться дважды. Количество чисел, делящихся одновременно на a[0] и на a[1], равно n / НОК(a[0], a[1]). Таким образом, для двух чисел

f(n, a) = n / a[0] + n / a[1] –  n / НОК(a[0], a[1])

3. Пусть a содержит три элемента. Тогда согласно принципу включения – исключения

f(n, a) = n / a[0] + n / a[1]  + n / a[2] – 

n / НОК(a[0], a[1]) –  n / НОК(a[1], a[2]) –  n / НОК(a[0], a[2]) + n / НОК(a[0], a[1], a[2])

Поскольку по условию задачи массив a содержит от 1 до 18 элементов, то перебрать все подмножества множества a можно не более чем за 218 операций. При этом если подмножество  содержит нечетное число элементов, то значение n / НОК() следует прибавлять к накапливаемой сумме (ответу), если четное – то вычитать.

Если при вычислении НОК() текущее значение НОК() станет больше n для некоторого j < k, то процесс вычисления НОК() можно завершить, так как тогда n / НОК() = 0.

 

ПРОГРАММА

 

#include <cstdio>

#include <vector>

using namespace std;

 

int gcd(int a, int b)

{

  return (!b) ? a : gcd(b,a % b);

}

 

int f(int N, vector<int> &a)

{

  int i, j, bits, temp, res = 0, n = (int)a.size();

  long long lcm;

  for(i = 1; i < (1<<n); i++)

  {

    lcm = 1; bits = 0;

    for(j = 0; j < n; j++)

      if (i & (1 << j))

      {

        bits++;

        temp = gcd(lcm,a[j]);

        lcm = lcm / temp * a[j];

        if (lcm > N) break;

      }

    if (bits % 2) res += N / lcm; else res -= N / lcm;

  }

  return res;

}

 

class Divisibility

{

public:

  int numDivisible(int l, int r, vector<int> a)

  {

    return f(r,a) - f(l - 1,a);

  }

};