Нахождение ближайшего числа, которое дает фактор простых чисел

Скажем, у меня есть номер, я могу найти все основные факторы, которые составляют это число. Например, 6000 составляет 2 ^ 4 * 3 * 5 ^ 3.

Если у меня есть число, которое не факторизуется хорошо (учитывая список приемлемых простых чисел), как я могу найти следующий ближайший номер? Например, учитывая число 5917, каково ближайшее число, которое множится со списком простых чисел 2, 3, 5, 7? Это 6000 в этом примере.

У меня есть что-то, что грубая сила найдет ответ, но должно быть более элегантное решение.

const UInt32 num = 5917;
const CVector<UInt32> primes = { 2, 3, 5, 7 };
const size_t size = primes.size();

UInt32 x = num;
while (x < num * 2)
{
    const UInt32 y = x;
    for(size_t i = 0; i < size && x > 1; ++i)
    {
        while(x % primes[i] == 0)
        {
            x /= primes[i];
        }
    }

    if (x == 1)
    {
        cout << "Found " << y << endl;
        break;
    }
    else
    {
        x = y + 1;
    }
}

ИЗМЕНИТЬ

Я создал тест, который использовал метод грубой силы и 3 метода, предоставленные в качестве ответов, и получил несколько неожиданные результаты. Все 4 версии дают правильные ответы (так спасибо за ваши вклады), однако метод грубой силы, казалось, был самым быстрым на порядок. Я пробовал несколько разных систем, компиляторов и архитектур, которые дали в основном согласованные результаты.

Тестовый код можно найти здесь: http://ideone.com/HAgDsF. Пожалуйста, не стесняйтесь делать предложения.

Ответ 1

Предлагаю следующее решение. Я предполагаю, что простые числа упорядочены от нижнего к большему. Я также использовал удобные типы vector и int.

vector<int> primes = { 2, 3, 5, 7 };
int num = 5917;
// initialize bestCandidate as a power of some prime greater than num
int bestCandidate = 1;
while (bestCandidate < num) bestCandidate *= primes[0];
set<int> s;
s.insert(1);
while (s.size()) {
    int current = *s.begin();
    s.erase(s.begin());
    for (auto p : primes) { // generate new candidates
        int newCandidate = current * p;
        if (newCandidate < num) {
            // new lower candidates should be stored.
            if (s.find(newCandidate) == s.end())
                s.insert(newCandidate);
        }
        else {
            if (newCandidate < bestCandidate) bestCandidate = newCandidate;
            break; // further iterations will generate only larger numbers
        }
    }
}
cout << bestCandidate;

Демо

Далее я хочу провести анализ предлагаемых решений. Позвольте мне использовать np как число простых чисел; n как число, чтобы найти ближайший результат; minP как минимальное число в списке.

  • Мое решение генерирует все возможные значения, которые меньше n. Новые значения генерируются из старых. Каждое значение используется только один раз для источника генерации. Если новое значение превышает n, оно считается действительным кандидатом. В случае, если список будет содержать все простые числа, меньшие, чем n, все же алгоритм может работать хорошо. Я не знаю довольно сложную формулу сложности времени для алгоритма, но это число действительных кандидатов ниже n, умноженное на журнал предыдущего фактора. Журнал поступает из set операций структуры данных. Мы можем избавиться от коэффициента Log, если n может быть достаточно малым, чтобы выделить массив размера n для флага, значения которого уже были сгенерированы, простой список может содержать значения источника генерации вместо заданного.

  • Ваше исходное решение имеет O (n (np + log minP (n))). Вы проверяете каждое число, которое должно быть действительным, а затем один за другим от n до 2n, платя np + log minP (n) для каждой проверки.

  • Рекурсивное решение от @anatolyg имеет большой недостаток в "посещении" некоторых допустимых номеров много раз, что очень неэффективно. Его можно исправить, введя флажки, указывающие, что число уже "посещено". Например, 12 = 2*2*3 будет посещаться с 6 = 2*3 и 4 = 2*2. Незначительные недостатки - это многочисленные переключение контекста и поддержка состояния каждого вызова. Решение имеет глобальную переменную, которая загромождает глобальное пространство имен, это можно решить, добавив параметр функции.

  • Решение @dasblinkenlight неэффективно, потому что уже "используемые" кандидаты принимаются для генерации новых кандидатов, производящих числа, уже присутствующие в наборе. Хотя я заимствовал идею с помощью set.

Основываясь на ответе @גלעד ברקן, я создал решение c++, которое действительно кажется асимптотически более эффективным, потому что нет фактора log, Однако я отказался работать с логарифмами double и оставил решение с целыми числами. Идея проста. У нас есть список продуктов ниже num. Каждый из продуктов генерируется из первых простых primesUsed простых чисел. Затем мы пытаемся сгенерировать новые продукты, используя следующий премьер. Такой подход гарантирует создание уникальных продуктов:

vector<int> primes = { 2, 3, 5, 7, 11, 17, 23 };
int num = 100005917;
int bestCandidate = INT_MAX;
list<pair<int, int> > ls;
ls.push_back(make_pair(1, 0));
while (ls.size()) {
    long long currentProd = ls.front().first;
    int primesUsed = ls.front().second;
    ls.pop_front();
    int currentPrime = primes[primesUsed];
    while (currentProd < num) {
        if(primesUsed < primes.size() - 1)
            ls.push_back(make_pair(currentProd, primesUsed + 1));
        currentProd *= currentPrime;
    }
    bestCandidate = min((long long)bestCandidate, currentProd);
}
cout << bestCandidate;

Демо

Ответ 2

Вместо того, чтобы пытаться ответить на повторный факторинг, вы можете попробовать создать все возможные продукты, пока не перечислите все продукты под target*minPrime, где minPrime - наименьшее число в вашем наборе.

Начните с набора, состоящего из 1. Каждая итерация пытается умножить каждое число в текущем наборе на каждое число. Если найден новый номер под max, он добавляется к текущему набору. Процесс повторяется до тех пор, пока не будут добавлены новые номера.

В вашем случае первое поколение будет

1 2 3 5 7

Следующее поколение будет

1 2 3 4 5 6 7 9 10 14 15 21 25 35 49 

После этого вы увидите

Поколение 3

1 2 3 4 5 6 7 8 9 10 12 14 15 18 20 21 25 27 28 30 35 42 45 49 50 63 70 75 98 105 125 147 175 245 343

Поколение 4

1 2 3 4 5 6 7 8 9 10 12 14 15 16 18 20 21 24 25 27 28 30 35 36 40 42 45 49 50 54 56 60 63 70 75 81 84 90 98 100 105 125 126 135 140 147 150 175 189 196 210 225 245 250 294 315 343 350 375 441 490 525 625 686 735 875 1029 1225 1715 2401 

и т.д. После двенадцати поколений ваш набор больше не будет расти, и в этот момент вы можете найти наименьшее значение выше цели.

Демо-версия

Ответ 3

Идея состоит в том, чтобы проверить все возможные продукты приемлемых простых чисел и выбрать лучшее.

Чтобы реализовать это, проще всего, хотя, вероятно, и не самым эффективным, использовать рекурсию. Сделайте рекурсивную функцию, которая "проверяет" временный продукт, добавляя все приемлемые простые числа один за другим. Чтобы запомнить лучший результат, проще всего использовать глобальную переменную.

int g_result;

void check(int num, int product, const vector<int>& primes)
{
    if (product >= num)
    {
        g_result = std::min(g_result, product);
    }
    else
    {
        for (int prime: primes)
            check(num, product * prime, primes);
    }
}

...
int main()
{
    g_result = INT_MAX;
    vector<int> primes = { 2, 3, 5, 7 };
    check(5917, 1, primes);
    std::cout << g_result;
}

Использование глобальной переменной - уродливый взлом; он достаточно хорош в этом простом примере, но не подходит для сложных (многопоточных) систем. Чтобы исключить глобальную переменную, введите функцию в класс и сделайте ее методом; и используйте переменную-член result вместо глобальной.

Примечание. Для удобства я использовал vector<int> вместо CVector<UInt32>.

Ответ 4

Взяв логарифм, мы можем рассматривать это как вариант проблемы суммы подмножества. Здесь приведен пример JavaScript, который перечисляет различные комбинации, которые просто передают целевую метку.

function f(target,primes){
  target = Math.log(target);
  primes = primes.map(function(x){ return Math.log(x); });

  var best = primes[0] * Math.ceil(target / primes[0]);
  var stack = [[0,0]];

  while (stack[0] !== undefined){
    var params = stack.pop();
    var t = params[0];
    var i = params[1];

    if (t > target){
      if (t < best){
        best = t;
      }
    } else if (i == primes.length - 1){
      var m = Math.ceil((target - t) / primes[i]);
      stack.push([t + m * primes[i],i + 1]);
    } else {
      t -= primes[i];
      while (t < target){
        t += primes[i];
        stack.push([t,i + 1]);
      }
    }
  }

  return Math.round(Math.pow(Math.E,best));
}

console.log(f(5917,[2,3,5,7]));