Страницы

Поиск по вопросам

Показаны сообщения с ярлыком факторизация. Показать все сообщения
Показаны сообщения с ярлыком факторизация. Показать все сообщения

среда, 1 января 2020 г.

Количество чисел n, у которых число делителей равно числу делителей n+1

#c_sharp #алгоритм #простые_числа #факторизация


Сама задача на первый взгляд кажется довольно простой:


  Найти количество таких чисел n (n<10^5), у которых число делителей равно числу
делителей числа n+1


Вот мое решение:  

static int NumberOfDividers(int number)
        {
            int counter = 0;
            for (int i = 1; i <= number; i++)
                if (number % i == 0)
                    counter++;
            return counter;

        }
        static void Main()
        {
            int counter = 0;
            for (int i = 1; i < 100000; i++)
            {
                if (NumberOfDividers(i) == NumberOfDividers(i + 1))
                    counter++;
            }
            Console.WriteLine(counter);
        }


Но алгоритм работает очень долго, какие есть варианты для его упрощения?
    


Ответы

Ответ 1



Для начала уберем лишние вычисления из основного цикла. Обратите внимание, что на каждой итерации вы вычисляете количество делителей для обоих чисел, хотя на каждой предыдущей итерации для меньшего числа значение уже было вычислено. void Main() { int counter = 0; int current = 0; for (int i = 1; i < 100000; i++) { int next = NumberOfDividers(i); if (current == next) { counter++; } current = next; } Console.WriteLine(counter);//10585 } Только это изменение уменьшит время работы вдвое. Теперь разберемся с вычислением количества делителей. Любое число делится на 1 и себя, поэтому сразу запишем 2 в счетчик, или 0, если это единица. static int NumberOfDividers(int number) { int counter = number == 1 ? 0 : 2; Также как и с простыми числами, нет смысла перебирать все предшествующие значения, достаточно перебрать значения до корня из заданного числа. Если есть делители меньше корня, то им будут соответствовать парные делители больше корня. double root = Math.Sqrt(number); Если число является квадратом, то один из делителей будет равен корню, проверяем и добавляем единицу в счетчик остальные делители парные. Для проверки нужна только целая часть корня, иначе при сравнении мы никогда не получим равенства (см. этот вопрос-ответ). Кроме того, если исходное число - единица, эта проверка добавит в счетчик ее единственный делитель и функция вернет 1, как и должна. int intRoot = (int)root; if(intRoot * intRoot == number) counter++; Остался основной цикл. Начинаем с 2, т.к. единица и само число уже учтены. Делители ходят парами, поэтому счетчик увеличиваем на 2. Исключением является корень из числа, но его мы уже учли. for (int i = 2; i < root; i++) if (number % i == 0) { counter +=2;//делители ходят парами. поэтому увеличиваем сразу на два. } return counter; } После всех манипуляций суммарное время выполнения, по грубым замерам*, уменьшилось с ~3 минут до ~0,4 секунды. Т.е. примерно в 450(!) раз. *стандартный тестовый стенд не собирал, данные со встроенного счетчика времени LiNQPad

Ответ 2



Я прошу прощения, но на C# пас. Зато могу набросать на C++ :) Итак, чтобы посчитать количество делителей - раскладываем число на простые сомножители; количество делителей после этого определяется как... как в этом ответе :) - там расписано. Искать делители просто - достаточно проверять простые числа до квадратного корня из числа, причем само число всякий раз уменьшаем делением на найденный простой делитель - для скорости. Быстрый квадратный корень передираем у Уоррена в "Алгоритмических трюках"; но можно использовать и обычный sqrt. Считаем для каждого числа один раз, запоминая значение для предыдущего числа. Все, собирая все вместе - #include #include #include using namespace std; unsigned int primes[] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317 }; inline unsigned long isqrt(unsigned long x) { unsigned long x1, g0, g1; if (x <= 1) return x; int s = 1; x1 = x - 1; if (x1 > 0xFFFF) { s = s + 8; x1 >>= 16; } if (x1 > 0xFF) { s = s + 4; x1 >>= 8; } if (x1 > 0xF) { s = s + 2; x1 >>= 4; } if (x1 > 0x3) { s = s + 1; } g0 = 1ll << s; g1 = (g0 +(x>>s)) >> 1; while( g1 < g0) { g0 = g1; g1 = (g0 + (x/g0)) >> 1; } return g0; } int factors(unsigned long m) { map fs; for(unsigned int i = 0; m > 1 && primes[i] <= isqrt(m);) { if (m%primes[i]) { ++i; continue; } m /= primes[i]; fs[primes[i]]++; } if (m > 1) fs[m]++; int count = 1; for(auto q: fs) count *= (q.second+1); return count; } int main(int argc, const char * argv[]) { int total = 0, last = 1; for(unsigned long n = 2; n < 100000; ++n) { int count = factors(n); if (last == count) { // cout << (n-1) << " vs " << n << " count = " << count << endl; ++total; } else last = count; } cout << total; } Считает за миллисекунды - см. https://ideone.com/QRI81U Еще раз прошу прощения за C++, а не C#...

суббота, 7 декабря 2019 г.

Реализация поиска простых делителей

#cpp #алгоритм #математика #инспекция_кода #факторизация


Всем привет, написал код по поиску простых чисел и запоминания этих чисел и их степеней:

int t = 0;
    int i = 2;
    int k = 1;
    int z = 0;
    vector q(10);
    vector ak(10);
    int l=phi(p);
    t = l;

    while (i <= t)
    {
        if (t%i == 0)
        {
            t = t / i;
            q[z] = i;
            ak[z] = k;
            k++;
            if (t%i != 0)
            {
                z++;
                k = 1;
            }
        }
        else
        {
            i = i + 1;
        }
    }
}


Получилось очень много кода. Подскажите, пожалуйста, как можно оптимизировать
    


Ответы

Ответ 1



Я бы не сказал, что его много, его просто мало :), но он не очень удобен и эффективен. Держать два массива для делителей и степеней - неудобно, тем более что это тесно взаимосвязанные вещи. Проще использовать map. Далее, перебирать все делители тоже смысла не имеет - ну зачем после проверки деления на 2 проверять, например, деление на 6? Идеально для скорости - хранить массив простых чисел и работать с ним; но если это кажется напрасной тратой места :) - то хотя бы проходите просто по нечетным числам, вынеся работу с двойками отдельно. Словом, я бы предложил такое: map factorize(unsigned int n) { map m; while(n > 1 && n%2 == 0) { m[2]++; n /= 2; } if (n > 1) for(unsigned int i = 3; i*i <= n;) { if (n%i == 0) { m[i]++; n /= i; continue; } i += 2; } if (n > 1) m[n]++; return m; } Еще раз - я бы этот код дополнил таблицей простых чисел... И еще - малое количество кода - не всегда означает эффективное решение. Нередко лучше написать в два раза больше кода, но получить в несколько раз более быстрое решение.

Ответ 2



Итак, сравниваем два метода. Полный код тут. Вкратце - чтоб не просто делители считать, но еще и использовать как-то наши vector и map, я еще и считаю общую сумму делителей и их степеней. Так, первое, что в голову пришло. Ну, и пришлось переписать немного код Qwertiy, чтоб лишнего поменьше делал... Проверяю примерно так: { muTimer mu; unsigned int total = 0; for(unsigned int n = 2; n < N; ++n) { auto m = Qwertiy(n); for(auto [p,k] : m) total += p + k; } mu.stop(); cout << "Qwertiy: " << total << " for " << mu.duration() << " ms\n"; } Заряжаем на 5 миллионов чисел: Harry : 2479739720 for 4377 ms Qwertiy: 2479739720 for 4056 ms Компилировал VC++2017, 64-разрядное приложение, запускал на своей Intel(R) Core(TM) i5-2500 CPU @ 3.30 GHz 8G RAM --- Windows 7 x64. Разница есть, но назвать ее критичной я не могу :) Вот если дописать резервирование памяти для вектора - тогда да, разница с 10% вырастает до 20%. Кстати, замена map на unordered_map только ухудшает дело. Update А вот очень показательный пример - работаем с большими числами; код с проверкой подряд и код с проверкой с помощью готовой таблицы простых чисел - см. тут - показывает, как эффективнее факторизовать: Harry : 647967786 for 9054 ms PHarry : 647967786 for 105 ms Qwertiy : 647967786 for 8975 ms VecPrimes: 647967786 for 95 ms Кстати, для больших чисел, когда главное - не во времени доступа к контейнеру, а в вычислениях - разница оказывается невелика.

Ответ 3



https://ideone.com/HVD59h #include #include #include #include #include using namespace std; vector < pair > dividers(unsigned n) { vector < pair > res; if (n <= 1) res.push_back(make_pair(n, 1)); for (unsigned q=2; q*q<=n; ++q) { if (n % q == 0) { unsigned d; for (d=0; !(n%q); ++d) n/=q; res.push_back(make_pair(q, d)); } } if (n > 1) res.push_back(make_pair(n, 1)); return res; } string fragment(const pair &p) { char res[256]; sprintf(res, p.second==1 ? "%d" : "%d^%d", p.first, p.second); return res; } void print(const vector < pair > &v) { transform(v.begin(), --v.end(), ostream_iterator(cout, " * "), fragment); cout << fragment(*v.rbegin()) << endl; } int main() { print(dividers(126)); print(dividers(1)); print(dividers(17)); print(dividers(128)); print(dividers(0)); print(dividers(121)); return 0; } 2 * 3^2 * 7 1 17 2^7 0 11^2

четверг, 11 октября 2018 г.

Реализация поиска простых делителей

Всем привет, написал код по поиску простых чисел и запоминания этих чисел и их степеней:
int t = 0; int i = 2; int k = 1; int z = 0; vector q(10); vector ak(10); int l=phi(p); t = l;
while (i <= t) { if (t%i == 0) { t = t / i; q[z] = i; ak[z] = k; k++; if (t%i != 0) { z++; k = 1; } } else { i = i + 1; } } }
Получилось очень много кода. Подскажите, пожалуйста, как можно оптимизировать


Ответ

Я бы не сказал, что его много, его просто мало :), но он не очень удобен и эффективен. Держать два массива для делителей и степеней - неудобно, тем более что это тесно взаимосвязанные вещи. Проще использовать map
Далее, перебирать все делители тоже смысла не имеет - ну зачем после проверки деления на 2 проверять, например, деление на 6? Идеально для скорости - хранить массив простых чисел и работать с ним; но если это кажется напрасной тратой места :) - то хотя бы проходите просто по нечетным числам, вынеся работу с двойками отдельно.
Словом, я бы предложил такое
map factorize(unsigned int n) { map m; while(n > 1 && n%2 == 0) { m[2]++; n /= 2; } if (n > 1) for(unsigned int i = 3; i*i <= n;) { if (n%i == 0) { m[i]++; n /= i; continue; } i += 2; } if (n > 1) m[n]++; return m; }
Еще раз - я бы этот код дополнил таблицей простых чисел...
И еще - малое количество кода - не всегда означает эффективное решение. Нередко лучше написать в два раза больше кода, но получить в несколько раз более быстрое решение.