#алгоритм #математика #числа
Задача. Даны два числа s и p, при этом s <= p. Требуется найти ближайший после s
делитель d числа p, такой что:
s <= d
p mod d = 0
если s != d, то p mod (s,d] != 0 // в дипазоне от s до d нет делителей
Например, для s = 7 и p = 33, d = 11.
Придумал два вот таких варианта:
// тупой перебор
int delimeter_1(int s, int p)
{
if(s <= p)
{
int d = s;
while(0 != p % d)
{
++d;
}
return d;
}
return p;
}
// перебор поумнее
int delimeter_2(int s, int p)
{
if(s <= p)
{
int q = p / s;
while(0 != p % q)
{
--q;
}
return p / q;
}
return p;
}
Пояснение. Например для числа 15 построим таблицу:
делители: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
частное : 15 7 5 3 3 2 2 1 1 1 1 1 1 1 1
остаток : 0 1 0 3 0 3 1 7 6 5 4 3 2 1 0
Так вот, в delimeter_1 мы движемся по строке делители, а в delimeter_2 по строке
частное - второй вариант получается чуть быстрее.
У меня есть подозрение, что возможно есть более быстрое и элегантное решение, основанное
на свойствах чисел, либо вообще без циклов, либо с более короткими циклами. Мне не
обязательно готовое решение, может кто-то подскажет где почитать на близкую тему или
выскажит интресные мысли и замечания.
Подумав, пришел к выводу, что аналитического неитерационного алгоритма не сущетсвует.
Такой алгоритм можно представить в виде некой функции f(q,s) = d, то есть q mod f(q,s)
= 0, или в виде уравнения q mod (s + k) = 0, где s + k = d. Как вытащить k мне не известно,
перешерстил все и ничего не нашел.
Ответы
Ответ 1
Решая перебором, следует комбинировать оба подхода. Для p/d < d применять первый,
для p/d > d второй. Т.е. сначала бежим по делителям, потом по частным. В случае с таблицей
по 15 будет от 1 до 4 (по делителю) первый подход, от 3 до 1 (по частным) второй.
Для варианта d = 1000 таблица будет из 1000 столбцов, а итераций от 1 до 31 и обратно
от 31 до 1 всего 62 (плюс-минус) максимум.
Дальше учитываем s и оптимизируем. Т.к. для первого подхода мы пройдем все делители
от s до sqrt(p) второй раз при проходе по частным эти делители проходить не нужно,
второй подход можно сразу начинать с s и до 1.
Получаем простой цикл от 1 до sqrt(p), просто для выявления ближайшего к s делителя
порядок не прямой.
В итоге:
int delimeter_3(int s, int p)
{
int i = s;
while(i > 1)
{
if(p % i==0) break;
if(i >= s) i++ else i--;
if(i > sqrt(p)) i = min(s, p / s) - 1;
}
return i >= s ? i
: i ? p / i
: p;
}
Ответ 2
Очевиден тот факт, что в случае нулевого остатка операция деления даёт сразу пару
делителей числа p (делитель и частное), причём половина делителей лежит слева от числа
q=[sqrt(p)], а половина - справа. При больших p неизбежно q << p, и части
(q и p-q) явно неравны, поэтому, к примеру, при факторизации стараются начинать с
меньших делителей.
Заметим, что при исходных данных (s=3, p=94) нахождение требуемого искомого делителя
"в лоб" потребует 91 операцию перебора, в то время как факторизация - всего лишь одну.
Причина кроется в низкой эффективности поиска делителей на интервале (q,p), потому
что их плотность там мала, а требуемый делитель 2 (который является ключом к частному
47) мы проскочили.
Т.е. в случае s < q есть риск превратить поиск делителя в рулетку.
Другое дело, если s>>q. Перебирая (в обратном порядке) частные от деления p на
t, t-1,...,2, где t = [q/s], мы повышаем шансы найти требуемый делитель. Т.е. в этом
случае параметр s может принести пользу.
Но что же делать при s < q? В этом случае остаётся выбор - пойти на факторизацию
или ограничиться проверкой делителей. Во втором случае можно начать с перебора делителей
по возрастанию, начиная с s+1, но перебор на интервале [q,p/s) вести с помошью таблицы
неиспользованных частных (в обратном порядке), а на интервале [p/s,p) -- непосредственно
вычисляя оставшиеся частные делением p на s, s-1, ....
Ответ 3
Вначале надо факторизовать число, потом построить список его делителей, отсортировать
его по возрастанию и найти, в какое место попадает s, и взять следующий справа делитель.
Факторизовать чем-нибудь отсюда, дальше проще. А перебирать задолбаешься на первом
же Р больше миллиона.