У меня есть школьная задачка о человеке по имени Дорел, который на день рождения получил число n
.
Он решил покрасить все натуральные числа от 1 до n в один цвет так, чтобы все его собственные делители числа были одного цвета с этим числом.
Задача состоит в том, чтобы выяснить, какое максимальное количество цветов можно использовать.
Например, с n = 5
у вас есть:
Итак, в этом примере нам нужно 4 цвета.
Упражнение можно найти под номером здесь, но оно на румынском языке.
Проблема возникает с простыми числами, поэтому я подумал о том, как вычислить, сколько простых чисел существует от 1
до n
, а затем добавить это к количеству необходимых цветов.
Я пробовал сложные решения, такие как реализация теста простоты Миллера-Рабина и Эратосфена, но для автоматических тестов на веб-сайте это все еще слишком медленно.
Я что-то упустил или единственный способ решить эту проблему — найти каждое простое число от 1 до n?
Моя реализация Эратосфена по примеру Википедии:
/* https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes#Overview */
void prime(uint n) {
if (n < 1) return;
/* Create a list of consecutive integers from 2 through n: (2, 3, 4, ..., n). */
uint size = n - 1;
uint *list = (uint *)malloc(sizeof(uint) * size);
for (uint i = 0; i < size; i++) {
list[i] = i + 2;
}
/* Initially, let p equal 2, the smallest prime number. */
uint p = 2;
uint c = 1;
while (c < n) {
/*
* Enumerate the multiples of p by counting in increments of p from 2p to n,
* and mark them in the list (these will be 2p, 3p, 4p, ...; the p itself should not be marked).
*/
for (uint i = c; i < size; i++) {
if (list[i] % p == 0) {
list[i] = 0;
}
}
/*
* Find the first number greater than p in the list that is not marked.
* If there was no such number, stop.
* Otherwise, let p now equal this new number (which is the next prime).
*/
while (c < n) {
if (list[c] > p) {
p = list[c++];
break;
}
c++;
}
}
/* the numbers remaining not marked in the list are all the primes below n */
for (uint i = 0; i < size; i++) {
if (list[i] != 0) {
printf("%d ", list[i]);
}
}
printf("\n\n");
}
Я не уверен, что вопрос здесь...
Во-первых, вы, вероятно, не хотите ceil(sqrt(n))
напрямую в качестве условия продолжения цикла for. Возможно, используемая вами stdlib не помечает их как чистые, что приведет к O(n) вызовам, вычисляющим верхнюю границу.
@Broman работает, компилируется и запускается, я думаю, это неправильно, потому что некоторые тесты терпят неудачу (на веб-сайте вы можете загрузить код и протестировать его, он дает много сбоев, и большинство из них терпят неудачу, потому что он слишком медленный)
@Borgleader Я пытаюсь выполнить упражнение, я думаю, чтобы решить эту проблему, мне нужно найти способ подсчитать, сколько простых чисел находится между 1 и n, проблема в том, что каждый подход, который я пробовал, слишком медленный.
Вы искали алгоритм? Не похоже, чтобы поиск простых чисел был новой проблемой. Очень легко найти быстрый код, который это делает.
Возможный дубликат Какой самый быстрый алгоритм для нахождения простых чисел?
Хотя считая количество простых чисел < n — это отдельная проблема от перечисление простых чисел < n, я считаю, что самый быстрый алгоритм для их подсчета — сначала перечислить их.
@Broman Я выполнил поиск и реализовал также тест на простоту Миллера Рабина, но он все еще был слишком медленным для тестов, я попробую библиотеки по этой ссылке, но я не думаю, что это решение, это упражнение для 9-го класса, я думаю решение намного проще
Возможно это: en.m.wikipedia.org/wiki/Sieve_of_Eratosthenes
Проблема в том, что вы используете алгоритм для одного числа снова и снова. Если вы хотите проверить много чисел, большую часть работы можно использовать повторно.
Я бы сделал что-то вроде этого:
bool * calculatePrimeArray(int n)
{
bool * ret = malloc(n*sizeof(*ret)+1);
for(int i=0; i<n*sizeof(*ret)+1; i++) ret[i]=true;
ret[0]=ret[1]=false;
int cur = 2;
while(cur < n/2+1) {
if (ret[cur])
for(int i=cur*2; i<n; i+=cur) ret[i] = 0;
cur++;
}
return ret;
}
Это возвращает логический массив ret, где ret[i] указывает, является ли я простым числом или нет.
Если вы хотите сделать его более удобным для кэширования, вы можете использовать битовые поля вместо логических переменных.
Я попытался скомпилировать его, и он застрял в цикле while, g++ без каких-либо аргументов.
@genesisxyz Пропустил строчку. Починил это.
хорошо, спасибо, я скоро представлю полное решение
Используя ответ @Bromax, я заставил его работать, и он набрал 100 баллов по всем тестам на веб-сайте:
#include <cstdlib>
#include <iostream>
#include <cmath>
#define PRIME 0
#define NOT_PRIME 1
bool *primes(int n) {
bool *ret = (bool *)calloc(n + 1, sizeof(bool));
ret[0] = ret[1] = NOT_PRIME;
uint cur = 2;
while (cur <= sqrt(n)) {
if (ret[cur] == PRIME) {
for (uint i = cur * cur; i <= n; i += cur) {
ret[i] = NOT_PRIME;
}
}
cur++;
}
return ret;
}
int main() {
FILE *input = NULL;
FILE *output = NULL;
input = fopen("primcolor.in", "r");
uint n;
fscanf(input, "%d", &n);
if (n < 1 || n > 50000000) {
fclose(input);
return 1;
}
output = fopen("primcolor.out", "w");
if (n <= 3) {
fprintf(output, "%d\n", n);
fclose(input);
fclose(output);
return 0;
}
uint colors = 2;
bool *a = primes(n);
for (uint i = (n / 2 + 1); i <= n; i++) {
if (a[i] == PRIME) {
colors++;
}
}
fprintf(output, "%d\n", colors);
fclose(input);
fclose(output);
return 0;
}
Как было предложено, самый быстрый подход — создать массив, который используется в качестве кеша для всех чисел от 0
до n
.
Это немного неясно. В чем проблема? Ваш код работает? Если да, то достаточно ли быстро?