Сортировка чисел в лексикографическом порядке их простых множителей

Я пытаюсь решить алгоритмическую задачу, которая требует от меня сортировки списка чисел размера N (N ‹ = 1e6) на основе лексикографического порядка их простых множителей. Каждое число в списке находится в [2,1e6].

Ссылка на проблему.

Например,

2 3 4 5 6

будет отсортирован по:

2 4 6 3 5

Их основные факторы показаны ниже:

2 = 2
3 = 3
4 = 2 * 2
5 = 5
6 = 2 * 3

Моя попытка:

Я могу найти правильное решение для этого, используя метод простой факторизации O (logn) для каждого из чисел и сохраняя его в массиве 1e6 * 21 2d, потому что все числа ‹ = 1e6 могут иметь не более 20 простых делителей, поскольку 2 ^ 20> 1e6.

После этого я сортирую каждое из чисел, используя лексографический порядок этих простых множителей.

Моя программа может работать хорошо при ограничении времени в 2 секунды, но использует слишком много памяти (ограничение памяти составляет 32 МБ).


Может ли кто-нибудь посоветовать мне лучший способ решить эту проблему?

p.s. Эта проблема была помечена как «поиск в глубину», но я все равно не понимаю, как это будет работать.


person Donald    schedule 21.11.2017    source источник
comment
В зависимости от того, какой язык программирования вы используете, вы можете уложиться в лимит памяти, не меняя свой подход. Всего факторов менее 4 миллионов. Таким образом, если каждый фактор составляет 32 бита (4 байта), это меньше 16 МБ. Другими словами, вместо массива фиксированного размера для каждого числа используйте массив, размер которого достаточен только для хранения коэффициентов.   -  person user3386109    schedule 21.11.2017
comment
@user3386109 user3386109 как бы вы сохранили коэффициенты 524288 (2 ^ 19)? Или 510510 (2*3*5*7*11*13*17)?   -  person Mark Ransom    schedule 21.11.2017
comment
@MarkRansom Я предлагаю, чтобы ОП использовала неоднородный массив.   -  person user3386109    schedule 21.11.2017
comment
не могли бы вы уточнить свой метод факторизации простых чисел O (logn) для каждого из чисел? Вы строите здесь сито с минимальным фактором?   -  person Will Ness    schedule 21.11.2017
comment
@Will Ness Я использовал метод быстрой факторизации, в котором используются наименьшие делители, как указано в этой ссылке: hackerearth.com/practice/math/number-theory/   -  person Donald    schedule 21.11.2017
comment
Благодарю. таким образом, это является решетом с минимальным фактором. нет проблем, я думаю, вы можете адаптировать к нему ответ Марка. Кстати, это не журнал (n) на число; это (log log n) ^ 2. Что, вероятно, очень близко на практике. :) Но для этого требуется половина размера исходного массива с точки зрения пространства. тем не менее, это должно быть управляемым. интересная проблема кстати. не могли бы вы дать ссылку на него?   -  person Will Ness    schedule 21.11.2017
comment
@Will Ness Спасибо за разъяснение временной сложности. На данный момент я не могу реализовать ответ Марка без конкретного примера. Я добавил ссылку на источник проблемы в свой пост.   -  person Donald    schedule 21.11.2017
comment
Вы хотите, чтобы я опубликовал некоторые разъяснения в ответ?   -  person Will Ness    schedule 21.11.2017
comment
@ Уилл Несс, я был бы очень признателен, если бы ты это сделал.   -  person Donald    schedule 21.11.2017
comment
Давайте продолжим обсуждение в чате.   -  person Will Ness    schedule 21.11.2017


Ответы (3)


Это звучит как проблема с разделами для меня. Первым шагом будет разбиение массива так, чтобы числа, делящиеся на 2, стояли первыми. Затем разделите эту группу на те, которые делятся на 2 во второй раз. Рекурсивно, пока у вас не будет пустой подгруппы. Теперь сделайте это снова с делителем 3. Продолжайте список простых чисел, пока не дойдете до sqrt(1e6) или пока не найдете все делители для каждого числа.

person Mark Ransom    schedule 21.11.2017
comment
Почему именно sqrt(1e6)? - person Donald; 21.11.2017
comment
@LanceHAOH, это самое большое число, которое можно без остатка разделить на 1e6. - person Mark Ransom; 21.11.2017
comment
Хм. Возьмем, к примеру, 22, 22 = 2 x 11, но sqrt(22) ‹ 5. Таким образом, мы не можем увидеть 11. Как это будет покрыто? - person Donald; 21.11.2017
comment
@LanceHAOH 2 меньше 5, поэтому вы будете делить на это. Каким бы ни был остаток, он будет гарантированно простым. Вы были правы, что позвонили мне по этому поводу, это крайний случай. - person Mark Ransom; 21.11.2017
comment
@MarkRansom, не могли бы вы добавить один пример для [2,3,4,5,6]? - person noman pouigt; 21.11.2017
comment
хорошее начало (я проверил), но вам нужно показать, как для {2,3,4,6,8,10} 6 и 10 будут напечатаны до 3. Сейчас это совсем не ясно, что так будет. ---- Кроме того, это будет эффективно реализовывать факторизацию через пробное деление на простые числа с O( sqrt(n)/log(n)) раз на число n , но OP говорит о факторизации O(log n) (есть ли вообще такая вещь ??). - person Will Ness; 21.11.2017
comment
@WillNess мое намерение состояло в том, чтобы дать отправную точку, а не решить проблему на 100%. Я думаю, что могу заставить это работать рекурсивно, как Quicksort, но я не придумал код, чтобы доказать это. Я никогда не видел эту проблему раньше. - person Mark Ransom; 21.11.2017
comment
@MarkRansom да, я тем временем возился с этим, и это выполнимо. Я просто говорю, что это не сразу видно из вашего ответа. Плюс вопрос о сложности... - person Will Ness; 21.11.2017

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

В одной таблице сохраните числа до 1e3 (1000) так, как вы их сейчас храните. Для этого потребуется массив 1e3 x 10 2d (1000 x 10 x 4 = 10000 байт).

Чтобы сохранить все простые множители для чисел до 1e6, вам нужно сохранить до 3 чисел для каждого (это 12 миллионов байт). Чтобы вычислить 3 числа, начните со списка простых множителей и перемножайте их до тех пор, пока вы не сможете умножить еще одно, не превысив 1000. Сохраните это в первой записи, затем сделайте то же самое с остатком и сохраните во втором числе. и если у вас осталось что-то, просто поместите его на 3-ю позицию (вам никогда не понадобится больше 3 - если бы у вас было 4, это означало бы, что последние два, умноженные вместе, будут больше 1000, что означало бы, что первые 2, умноженные вместе, будут быть ‹ 1000, в этом случае они не будут храниться отдельно). Если в списке есть простой множитель больше 1000, вам нужно только 2, потому что все остальные будут умножаться на ‹ 1000.

Чтобы получить исходный список простых множителей для записи, возьмите каждое из ваших трех чисел (которые будут простыми или составными числами 1000 или меньше или простыми числами> 1000), если они меньше 1000, найдите их простые множители в маленькой таблице и если не принимать их как есть, можно и восстановить список.

Например, для хранения 515130 (2*3*5*7*11*223)

1st number: 210 (2*3*5*7) can't multiply by 11 without going over 1000
2nd number: 11 (prime) can't multiply by 223 without going over
3rd number: 223

667023 (3*7*23*1381)

1st: 438 (3*7*23)
2nd: 1381 (prime)

Это будет работать, даже если список простых множителей не отсортирован.

person samgak    schedule 21.11.2017

На самом деле, простая модификация моего алгоритма помогла. Вместо того, чтобы сохранять первичную факторизацию каждого целого числа, я использовал то, что у меня уже было, а именно метод первичной факторизации, работающий за O(logn). Я создал собственный метод сортировки, который использует метод факторизации для факторизации двух целых чисел, сравнивая их простые множители. Следовательно, временная сложность осталась прежней, и не было необходимости хранить простую факторизацию любого целого числа.

Для тех, кому интересно узнать, как работает этот метод быстрой факторизации, вот ссылка ссылка (см. метод, использующий наименьшие делители).

Для будущих читателей, которые столкнутся с той же проблемой, вот мой принятый код:

#include<cstdio>
#include<algorithm>

#define FACTOR_LIM (int) 1e6+2 // used by preFactor(n). Defined as <= n <= 1e8

using namespace std;

int lowestDiv[FACTOR_LIM+1], a[FACTOR_LIM], n;

void preFactor(int n) {
    int root = 2;
    for(int i = 2; i*i <= n; i++) {
        if(lowestDiv[i]) continue;
        root = lowestDiv[i] = i;
        for(int j = i*i; j <= n; j+=i) {
            lowestDiv[j] = (lowestDiv[j]) ? lowestDiv[j] : i;
        }
    }
    for(int i = root; i <= n; i++) {
        if(!lowestDiv[i]) {
            lowestDiv[i] = i;
        }
    }
}

bool cmp(const int i, const int j) {
    int x = i, y = j;
    while (x != y) {
        int p = lowestDiv[x];
        int q = lowestDiv[y];
        if (p != q) return p < q;
        x /= p;
        y /= q;
    }
    return false;
}

int main() {
    preFactor(FACTOR_LIM-1);
    scanf("%d",&n);

    for(int i = 0; i < n; i++) {
        scanf("%d",&a[i]);
    }
    sort(a,a+n,cmp);

    for(int i = 0; i < n; i++) {
        printf("%d\n",a[i]);
    }
    return 0;
}
person Donald    schedule 27.11.2017