Подтвердить что ты не робот

Как быстро оценить нулевые множества?

Этот недавний код для игры в гольф задал возможности быстрой реализации на C следующим (предполагая, что n является целым без знака):

if (n==6 || n==8 || n==10 || n==12 || n==14 || n==16 || n==18 || n==20)

Одно из возможных упрощений состоит в том, чтобы заметить, что числа a[]={6,8,10,12,14,16,18,20} образуют арифметическую прогрессию , поэтому меняя диапазон, а затем используя побитовые трюки

if (((n - 6) & 14) + 6 == n)

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

Теперь я спрашиваю, что является аналогичной элегантной (и, надеюсь, одинаково эффективной) реализацией

if (n==3 || n==5 || n==11 || n==29 || n==83 || n==245 || n==731 || n==2189)

Подсказка: на этот раз цифры a[k] образуют геометрическую прогрессию: a[k]=2+3^k.

Я предполагаю, что в общем случае нельзя сделать лучше, чем сортировать числа a[k], а затем выполнить логарифмический поиск, чтобы проверить, является ли n членом отсортированного массива.

4b9b3361

Ответ 1

if ((n > 2) && (2187 % (n - 2) == 0))

Проверяет, является ли (n - 2) мощностью 3 и меньше или равно 2187 (3 до степени 7)

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

Ответ 2

Это очень похоже на распознавание мощности по три, и вы можете адаптировать, например, это решение

bool test(unsigned x) {
    x -= 2;
    if (x > 2187)
        return 0;
    if (x > 243)
        x *= 0xd2b3183b;
    return x <= 243 && ((x * 0x71c5) & 0x5145) == 0x5145;
}

При заданном диапазоне это может быть дополнительно упрощено (найдено грубой силой):

bool test2(unsigned x) {
  x -= 2;
  return x <= 2187 && (((x * 0x4be55) & 0xd2514105) == 5);
}

Ответ 3

Подсказка: на этот раз цифры a[k] образуют геометрическую прогрессию: a[k]=2+3^k.

n = 2 + 3^k
n - 2 = 3^k

(n - 2) / 3^k = 1
(n - 2) % 3^k = 0

k = 0 ~ n-2 = 3^0 = 1, n = 3
k = 1 ~ n-2 = 3^1 = 3, n = 5
k = 2 ~ n-2 = 3^3 = 9, n = 11

if (n > 2 && isPow(n-2, 3))

с определением функции isPow(x,y)

bool isPow(unsigned long x, unsigned int y)
{
    while (x % y == 0)
    {
        x /= y;
    }

    return x == 1;
}

n = n  ~ n-2 = 3^k/3 = 3^(k-1)/3 = .. = 3^1/3 = 1%3 != 0
n = 11 ~ n-2 = 9/3 = 3/3 = 1%3 != 0
n = 5  ~ n-2 = 3/3 = 1%3 != 0
n = 3  ~ n-2 = 1%3 != 0

Аналогично мы можем вычесть k..

int k = findK(n-2, 3);

int findK(unsigned long x, unsigned int y)
{
    unsigned int k = 0;

    while (x % y == 0)
    {
        x /= y;
        k++;
    }

    if (x == 1) return k;
    else return (-1);
}

n - 2 = 3 * 3^(k-1)       for k > 0
(n - 2) / 3 = 3^(k-1)
(n - 2) / 3 / 3 = 3^(k-2)
(n - 2) / 3 / 3 / 3 = 3^(k-3)
(n - 2) / 3 / 3 / 3 / 3 = 3^(k-4)
..
(n - 2) / 3 / 3 / ..i times = 3^(k-i)
..
(n - 2) / 3 / 3 / ..k times = 3^0 = 1

Ответ 4

Обнаружена аналогичная проблема в соответствующем сообщении: Вы можете использовать std::find

bool found = (std::find(my_var.begin(), my_var.end(), my_variable) != my_var.end());
// my_var is a list of elements.

(убедитесь, что вы включили <algorithm> ).

Для такого рода вещей, в 99% случаев, есть библиотека, которая выполняет эту работу для вас.

Ответ 5

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

Просто вставьте значения, которые вы хотите сравнить, и count значение в наборе. Если он найден, это одно из ваших проверенных значений.

std::unordered_set< int > values;
values.insert( 3 );
values.insert( 5 );
values.insert( 11 );
values.insert( 29 );
values.insert( 83 );
values.insert( 245 );
values.insert( 731 );
values.insert( 2189 );

...

if( values.count( input ) )
    std::cout << "Value is in set.\n";
else
    std::cout << "Value is NOT in set.\n";

Ответ 6

Вот что я придумал:

bool b;
if (n >= 3 && n <= 2189)
{
    double d = std::log(n - 2)/std::log(3); // log₃(n - 2)
    b = std::trunc(d) == d; // Checks to see if this is an integer
    // If ‘b’ then ‘n == a[log₃(n - 2)]’
}
else b = false;

if (b)
{
    // your code
}

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

Конечно, это будет не так быстро, как целочисленная арифметика, она не подходит для выражения в выражении, вероятно, быстрее, чем какой-либо поиск массива, особенно если вы хотите увеличить максимальное значение n.

EDIT Я тестировал свой код (без оптимизации включен) и в среднем (для всех n меньше или равно 2189) моя версия заняла 185.849 нс, а версия || заняла 116.546 нс ( запуск моей программы несколько раз дал аналогичные результаты), так что это не быстрее. Также по какой-то причудливой причине он устанавливает b в false, когда n == 245 (это должно быть верно).