Как найти делитель, чтобы максимизировать остаток?
Учитывая два числа n
и k
, найдите x
, 1 <= x <= k
, который максимизирует остаток n % x
.
Например, n = 20 и k = 10, решение равно x = 7, потому что остаток 20% 7 = 6 максимален.
Мое решение:
int n, k;
cin >> n >> k;
int max = 0;
for(int i = 1; i <= k; ++i)
{
int xx = n - (n / i) * i; // or int xx = n % i;
if(max < xx)
max = xx;
}
cout << max << endl;
Но мое решение O(k)
. Есть ли более эффективное решение этого?
Ответы
Ответ 1
Не асимптотически быстрее, но быстрее, просто отступая назад и останавливаясь, когда вы знаете, что не можете сделать лучше.
Предположим, что k
меньше n
(иначе вывести k
).
int max = 0;
for(int i = k; i > 0 ; --i)
{
int xx = n - (n / i) * i; // or int xx = n % i;
if(max < xx)
max = xx;
if (i < max)
break; // all remaining values will be smaller than max, so break out!
}
cout << max << endl;
(Это может быть дополнительно улучшено путем выполнения цикла for до тех пор, пока i > max
, тем самым исключая один условный оператор, но я написал его таким образом, чтобы сделать его более очевидным)
Кроме того, проверьте книгу Garey and Johnson Computers и Intractability, чтобы убедиться, что это не NP-Complete (я уверен, что я помню некоторую проблему в этой книге, которая очень похожа на это). Я бы сделал это, прежде чем вкладывать слишком много усилий, пытаясь найти лучшие решения.
Ответ 2
Эта проблема эквивалентна определению максимума функции f(x)=n%x
в заданном диапазоне. Посмотрим, как выглядит эта функция:
![f (x) = n% x]()
Очевидно, что мы могли бы получить максимум раньше, если мы начнем с x=k
, а затем уменьшим x
, пока он имеет смысл (до x=max+1
). Также эта диаграмма показывает, что для x
больше, чем sqrt(n)
, нам не нужно последовательно уменьшать x
. Вместо этого мы могли сразу перейти к предыдущему локальному максимуму.
int maxmod(const int n, int k)
{
int max = 0;
while (k > max + 1 && k > 4.0 * std::sqrt(n))
{
max = std::max(max, n % k);
k = std::min(k - 1, 1 + n / (1 + n / k));
}
for (; k > max + 1; --k)
max = std::max(max, n % k);
return max;
}
Магическая константа 4.0
позволяет повысить производительность за счет уменьшения количества итераций первого (дорогого) цикла.
Худшая временная сложность может быть оценена как O (min (k, sqrt (n))). Но для достаточно большой k
эта оценка, вероятно, слишком пессимистична: мы могли бы найти максимум намного раньше, и если k
значительно больше, чем sqrt(n)
, нам нужно только 1 или 2 итерации, чтобы найти его.
Я провел несколько тестов, чтобы определить, сколько итераций требуется в худшем случае для разных значений n
:
n max.iterations (both/loop1/loop2)
10^1..10^2 11 2 11
10^2..10^3 20 3 20
10^3..10^4 42 5 42
10^4..10^5 94 11 94
10^5..10^6 196 23 196
up to 10^7 379 43 379
up to 10^8 722 83 722
up to 10^9 1269 157 1269
Скорость роста заметно лучше, чем O (sqrt (n)).
Ответ 3
При k > n задача тривиальна (возьмем x = n + 1).
Для k < n, подумайте о графике остатков n% x. Он выглядит одинаково для всех n: остатки падают до нуля на гармониках n: n/2, n/3, n/4, после чего они подпрыгивают, затем плавно уменьшаются к следующей гармонике.
Решение - самый правый локальный максимум ниже k. В качестве формулы x = n//((n//k)+1)+1
(где //
- целочисленное деление).
![введите описание изображения здесь]()
Ответ 4
Хорошая маленькая головоломка!
Начиная с двух тривиальных случаев.
для n < k
: any x
s.t. n < x <= k
решает.
для n = k
: x = floor(k / 2) + 1
решает.
Мои попытки.
для n > k
:
x = n
while (x > k) {
x = ceil(n / 2)
}
^ ---- Не работает.
-
x = floor(float(n) / (floor(float(n) / k) + 1)) + 1
-
x = ceil(float(n) / (floor(float(n) / k) + 1)) - 1
^ ---- "Закрыть" (что бы это ни значило), но не сработало.
Моя гордость побуждает меня упомянуть, что я первым использовал самую большую k
-ограниченную гармонику, данную 1.
Решение.
В строках с другими ответами я просто проверяю гармоники (термин любезность @ColonelPanic) n
меньше k
, ограничиваясь настоящим максимальным значением (любезно предоставлено @TheGreatContini). Это лучшее из обоих миров, и я успешно тестировал со случайными целыми числами от 0 до 10000000.
int maximalModulus(int n, int k) {
if (n < k) {
return n;
}
else if (n == k) {
return n % (k / 2 + 1);
}
else {
int max = -1;
int i = (n / k) + 1;
int x = 1;
while (x > max + 1) {
x = (n / i) + 1;
if (n%x > max) {
max = n%x;
}
++i;
}
return max;
}
}
Тесты производительности:
http://cpp.sh/72q6
Пример вывода:
Average number of loops:
bruteForce: 516
theGreatContini: 242.8
evgenyKluev: 2.28
maximalModulus: 1.36 // My solution
Ответ 5
машет руками
Никакое значение x
, которое является фактором n
, может давать максимум n%x
, так как если x
является фактором n
, то n%x=0
.
Следовательно, вам нужна процедура, которая позволяет избежать любого x
, являющегося фактором n
. Но это означает, что вам нужен простой способ узнать, является ли фактор x
фактором. Если бы это было возможно, вы могли бы сделать простой простой факторизации.
Так как не является известным простым способом сделать основную факторизацию, не может быть "простого" способа решить вашу проблему (я не знаю, я думаю, вы собираетесь найти одну формулу, какой-то поиск будет необходим).
Тем не менее, в простой литературе факторизации есть хитрые способы быстрого получения факторов относительно наивного поиска, поэтому, возможно, это может быть использовано для ответа на ваш вопрос.
Ответ 6
Я ошибаюсь точно, но мне кажется, что это зависит от того, есть ли n < k
или нет.
Я имею в виду, если n < k
, n%(n+1)
дает вам максимум, поэтому x = (n+1)
.
Ну, с другой стороны, вы можете начать с j = k
и вернуться к оценке n%j
, пока она не станет равной n
, таким образом x = j
- это то, что вы ищете, и вы получите ее в макс. k
шагов... Слишком много, не так ли?