Целое расстояние - PullRequest
       5

Целое расстояние

5 голосов
/ 13 ноября 2011

Как одна операция между двумя натуральными числами, мы понимаем умножение одного из чисел на некоторое простое число или деление его на такое (при условии, что оно может быть разделено на это простое число без остатка).Расстояние между a и b, обозначаемое как d (a, b), представляет собой минимальное количество операций, необходимых для преобразования числа a в число b.Например, d (69,42) = 3.

Имейте в виду, что наша функция d действительно имеет характеристики расстояния - для любых положительных целых чисел a, b и c мы получаем:

a) d (a, a) == 0

b) d (a, b) == d (b, a)

c) неравенствотреугольник d (a, b) + d (b, c)> = d (a, c) выполнен.

Вам будет дана последовательность положительных чисел a_1, a_2, ...,a_n.Для каждого a_i из них выведите такое значение a_j (j! = I), что d (a_i, a_j) будет как можно ниже.Например, последовательность длины 6: {1,2,3,4,5,6} должна вывести {2,1,1,2,1,2}.

Это кажется действительно сложныммне.Я думаю, что было бы полезно:

a) если a_i простое, мы не можем сделать что-то меньшее, чем a_i (если оно не равно 1), поэтому единственной разрешенной операцией является умножение.Поэтому, если в нашем наборе есть 1, для каждого простого числа d (this_number, 1) наименьшее.

b) также для 1 d (1, any_prime_number) наименьшее.

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

Это все, что я могу вывести.Хуже всего то, что я знаю, что такой алгоритм будет работать вечно и проверять все возможности ... Не могли бы вы попытаться помочь мне с этим?Как это сделать?

Ответы [ 4 ]

5 голосов
/ 13 ноября 2011

Действительно, вы можете представить любое число N как 2 ^ n1 * 3 ^ n2 * 5 ^ n3 * 7 ^ n4 * ... (большинство из n - нули).

Таким образом вы устанавливаете соответствие между числом N и бесконечной последовательностью (n1, n2, n3, ...).

Обратите внимание, что ваша операция просто добавляет или вычитает 1 точно в одном из мест соответствующей последовательности.

Пусть N и M - два числа, а их последовательности (n1, n2, n3, ...) и (m1, m2, m3, ...). Расстояние между двумя числами действительно не что иное, как | n1 - m1 | + | n2 - м2 | + ...

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


Edit:
На самом деле вам не нужна точная позиция вашего простого множителя: вам просто нужно знать, какова степень для каждого из простых делителей.


Edit:
это простая процедура для преобразования числа в представление цепочки:

#include <map>

typedef std::map<unsigned int, unsigned int> ChainRepresentation;
// maps prime factor -> exponent, default exponent is of course 0

void convertToListRepresentation(int n, ChainRepresentation& r)
{
    // find a divisor
    int d = 2;

    while (n > 1)
    {
        for (; n % d; d++)
        {
            if (n/d < d) // n is prime
            {
                r[n]++;
                return;
            }
        }

        r[d]++;
        n /= d;
    }
}

Edit:
... и код расстояния:

#include <set>

unsigned int chainDistance(ChainRepresentation& c1, ChainRepresentation& c2)
{
    if (&c1 == &c2)
        return 0; // protect from modification done by [] during self-comparison

    int result = 0;

    std::set<unsigned int> visited;
    for (ChainRepresentation::const_iterator it = c1.begin(); it != c1.end(); ++it)
    {
        unsigned int factor = it->first;
        unsigned int exponent = it->second;
        unsigned int exponent2 = c2[factor];
        unsigned int expabsdiff = (exponent > exponent2) ?
                       exponent - exponent2 : exponent2 - exponent;
        result += expabsdiff;
        visited.insert(factor);
    }

    for (ChainRepresentation::const_iterator it = c2.begin(); it != c2.end(); ++it)
    {
        unsigned int factor = it->first;
        if (visited.find(factor) != visited.end())
            continue;
        unsigned int exponent2 = it->second;
        // unsigned int exponent = 0;
        result += exponent2;
    }

    return result;
}
2 голосов
/ 13 ноября 2011

Для заданных пределов: 100_000 чисел, не превышающих миллион, работает самый простой алгоритм (1e10 вызывает distance()):

Для каждого числа в последовательности выведите его ближайшего соседа (как определеноминимальное расстояние):

solution = []
for i, ai in enumerate(numbers):
    all_except_i = (aj for j, aj in enumerate(numbers) if j != i)
    solution.append(min(all_except_i, key=lambda x: distance(x, ai)))
print(', '.join(map(str, solution)))

Где distance() можно рассчитать как (см. @ объяснение Влада ):

def distance(a, b):
    """
    a = p1**n1 * p2**n2 * p3**n3 ...
    b = p1**m1 * p2**m2 * p3**m3 ...

    distance = |m1-n1| + |m2-n2| + |m3-n3| ...
    """
    diff = Counter(prime_factors(b))
    diff.subtract(prime_factors(a))
    return sum(abs(d) for d in diff.values())

Где prime_factors() возвращает простые множителичисла с соответствующими кратностями {p1: n1, p2: n2, ...}:

uniq_primes_factors = dict(islice(prime_factors_gen(), max(numbers)))

def prime_factors(n):
    return dict(multiplicities(n, uniq_primes_factors[n]))

Где multiplicities() функция задана n и ее множители возвращают их с соответствующими им кратностями (сколько раз множитель делит число без остатка):

def multiplicities(n, factors):
    assert n > 0
    for prime in factors:
        alpha = 0 # multiplicity of `prime` in `n`
        q, r = divmod(n, prime)
        while r == 0: # `prime` is a factor of `n`
            n = q
            alpha += 1
            q, r = divmod(n, prime)
        yield prime, alpha

prime_factors_gen() дает простые множители для каждого натурального числа.Он использует алгоритм Sieve of Eratosthenes для поиска простых чисел.Реализация основана на gen_primes() функции @Eli Bendersky :

def prime_factors_gen():
    """Yield prime factors for each natural number."""
    D = defaultdict(list) # nonprime -> prime factors of `nonprime`
    D[1] = [] # `1` has no prime factors
    for q in count(1): # Sieve of Eratosthenes algorithm
        if q not in D: # `q` is a prime number
            D[q + q] = [q]
            yield q, [q]
        else: # q is a composite
            for p in D[q]: # `p` is a factor of `q`: `q == m*p`
                # therefore `p` is a factor of `p + q == p + m*p` too
                D[p + q].append(p)
            yield q, D[q]
            del D[q]

См. полный пример на Python .

Вывод

2, 1, 1, 2, 1, 2
1 голос
/ 13 ноября 2011

Без ограничений на то, насколько большими могут быть ваши числа и сколько чисел может быть на входе, мы не можем на самом деле сделать вывод, что для завершения потребуется «вечность». Я испытываю желание предложить самое «очевидное» решение, которое я могу придумать

  1. Учитывая факторизацию чисел, очень легко найти их расстояние

    60 = (2^2)*(3^1)*(5^1)*(7^0)
    42 = (2^1)*(3^1)*(5^0)*(7^1)
    distance = 3
    

    Расчет этой факторизации с использованием простого деления проб должен занимать не более O (sqrt (N)) времени на число, где N - это разлагаемое число.

  2. Учитывая факторизации, у вас есть только O (n ^ 2) комбинаций, о которых нужно беспокоиться, где n - количество чисел. Если вы храните все факторизации так, чтобы вычислять их только один раз, этот шаг не должен занимать так много времени, если у вас действительно большое количество чисел.


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

0 голосов
/ 13 ноября 2011

На самом деле не задумывался об этом, но мне кажется, что для перехода от простого A к простому B вы умножаете A * B, а затем делите на A.

Если вы таким образом разбиваете первичные непростые A & B на их простые факторы, вычленяете общие простые факторы, а затем используете технику из первого абзаца для преобразования уникальных простых чисел, вы должны следовать минимальному пути к добраться от А до Б.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...