Головоломка, которая бросает вызов подходу грубой силы?
Я купил пустой DVD, чтобы записать мое любимое телешоу. Он поставляется с 20-значными наклейками. 2 каждого из "0" - "9".
Я подумал, что было бы неплохо дать цифровую метку моей новой коллекции DVD. Я записал наклейку "1" на своем первом записанном DVD и положил 19 оставшихся наклейки в ящик.
На следующий день я купил еще один пустой DVD (получив на нем 20 новых стикеров), и после записи шоу я назвал его "2".
И потом я начал задаваться вопросом: когда наклейки закончатся, и я больше не смогу обозначить DVD?
Несколько строк Python, нет?
Можете ли вы предоставить код, который решает эту проблему с разумным временем выполнения?
Изменить: грубая сила просто займет слишком много времени. Пожалуйста, улучшите ваш алгоритм, чтобы ваш код вернул правильный ответ, скажем, в минуту?
Дополнительный кредит: что, если на DVD-диски было три наклейки каждой цифры?
Ответы
Ответ 1
Полностью новое решение. В 6 баджллионов раз быстрее, чем первый.
time { python clean.py ; }
0: 0
1: 199990
2: 1999919999999980
3: 19999199999999919999999970
4: 199991999999999199999999919999999960
5: 1999919999999991999999999199999999919999999950
6: 19999199999999919999999991999999999199999999919999999940
7: 199991999999999199999999919999999991999999999199999999919999999930
8: 1999919999999991999999999199999999919999999991999999999199999999919999999920
9: 19999199999999919999999991999999999199999999919999999991999999999199999999919999999918
real 0m0.777s
user 0m0.772s
sys 0m0.004s
код:
cache = {}
def how_many_used(n):
if n in cache:
return cache[n]
result = 0
if int(n) >= 10:
if n[0] == '1':
result += int(n[1:]) + 1
result += how_many_used(str(int(n[1:])))
result += how_many_used(str(int(str(int(n[0])-1) + "9"*(len(n) - 1))))
else:
result += 1 if n >= '1' else 0
if n.endswith("9" * (len(n)-0)) or n.endswith("0" * (len(n)-1)):
cache[n] = result
return result
def how_many_have(i, stickers):
return int(i) * stickers
def end_state(i, stickers):
if i == '':
return 0
return how_many_have(i, stickers) - how_many_used(i)
cache2 = {}
def lowest_state(i, stickers):
if stickers <= 0:
return end_state(i, stickers)
if i in ('', '0'):
return 0
if (i, stickers) in cache2:
return cache2[(i, stickers)]
lowest_candidats = []
tail9 = '9' * (len(i)-1)
if i[0] == '1':
tail = str(int('0'+i[1:]))
lowest_candidats.append(end_state(str(10**(len(i) - 1)), stickers))
lowest_candidats.append(lowest_state(tail, stickers - 1) + end_state(str(10**(len(i) - 1)), stickers))
else:
tail = str(int(i[0])-1) + tail9
series = end_state(tail9, stickers)
if series < 0:
lowest_candidats.append(lowest_state(str(int('0'+i[1:])), stickers) + end_state(i[0] + '0'*(len(i)-1), stickers))
lowest_candidats.append(lowest_state(tail, stickers))
result = min(lowest_candidats)
cache2[(i, stickers)] = result
return result
def solve(stickers):
i=1
while lowest_state(str(i), stickers) >= 0:
i *= 2
top = i
bottom = 0
center = 0
while top - bottom > 1:
center = (top + bottom) / 2
if lowest_state(str(center), stickers) >= 0:
bottom = center
else:
top = center
if lowest_state(str(top), stickers) >= 0:
return top
else:
return bottom
import sys
sys.setrecursionlimit(sys.getrecursionlimit() * 10)
for i in xrange(10):
print "%d: %d" % (i, solve(i))
Ответ 2
Это старое решение, полностью новое решение на 6 баджейл раз быстрее находится внизу.
Решение:
time { python solution.py; }
0: 0
1: 199990
2: 1999919999999980
3: 19999199999999919999999970
4: 199991999999999199999999919999999960
5: 1999919999999991999999999199999999919999999950
6: 19999199999999919999999991999999999199999999919999999940
7: 199991999999999199999999919999999991999999999199999999919999999930
8: 1999919999999991999999999199999999919999999991999999999199999999919999999920
9: 19999199999999919999999991999999999199999999919999999991999999999199999999919999999918
real 1m53.493s
user 1m53.183s
sys 0m0.036s
код:
OPTIMIZE_1 = True # we assum that '1' will run out first (It easy to prove anyway)
if OPTIMIZE_1:
NUMBERS = [1]
else:
NUMBERS = range(10)
def how_many_have(dight, n, stickers):
return stickers * n
cache = {}
def how_many_used(dight, n):
if (dight, n) in cache:
return cache[(dight,n)]
result = 0
if dight == "0":
if OPTIMIZE_1:
return 0
else:
assert(False)
#TODO
else:
if int(n) >= 10:
if n[0] == dight:
result += int(n[1:]) + 1
result += how_many_used(dight, str(int(n[1:])))
result += how_many_used(dight, str(int(str(int(n[0])-1) + "9"*(len(n) - 1))))
else:
result += 1 if n >= dight else 0
if n.endswith("9" * (len(n)-4)): # '4' constant was pick out based on preformence tests
cache[(dight, n)] = result
return result
def best_jump(i, stickers_left):
no_of_dights = len(str(i))
return max(1, min(
stickers_left / no_of_dights,
10 ** no_of_dights - i - 1,
))
def solve(stickers):
i = 0
stickers_left = 0
while stickers_left >= 0:
i += best_jump(i, stickers_left)
stickers_left = min(map(
lambda x: how_many_have(x, i, stickers) - how_many_used(str(x), str(i)),
NUMBERS
))
return i - 1
for stickers in range(10):
print '%d: %d' % (stickers, solve(stickers))
Докажите, что сначала "1" закончится:
def(number, position):
""" when number[position] is const, this function is injection """
if number[position] > "1":
return (position, number[:position]+"1"+number[position+1:])
else:
return (position, str(int(number[:position])-1)+"1"+number[position+1:])
Ответ 3
Вот доказательство существования решения:
Предполагая, что вы когда-нибудь доберетесь до 21-значного числа, вы начнете проигрывать стикер с каждым приобретенным вами DVD-диском и надписью ((+20) + (-21)
).
Неважно, сколько наклеек вы накопили до этого момента. Отсюда все это будет вниз по вашему стикеру, и вы в конечном итоге закончите.
Ответ 4
здесь быстрый и грязный питон script:
#!/bin/env python
disc = 0
stickers = {
0: 0, 1: 0,
2: 0, 3: 0,
4: 0, 5: 0,
6: 0, 7: 0,
8: 0, 9: 0 }
def buyDisc():
global disc
disc += 1
for k in stickers.keys():
stickers[k] += 1
def labelDisc():
lbl = str(disc)
for c in lbl:
if(stickers[int(c)] <= 0): return False;
stickers[int(c)] -= 1;
return True
while True:
buyDisc()
if not labelDisc(): break
print("No stickers left after " + str(disc) + " discs.")
print("Remaining stickers: " + str(stickers))
Я не знаю, дает ли он правильный результат. если вы найдете логические ошибки, прокомментируйте
результат с отладочным результатом:
Bought disc 199991. Labels:
Remaining stickers: {0: 111102, 1: 0, 2: 99992, 3: 99992, 4: 99992, 5: 99997, 6: 99992, 7: 99992, 8: 99992, 9: 100024}
Ответ 5
Результаты для любой базы N и количества наклеек на цифру на DVD "S":
N\S ] 1 | 2 | 3 | 4 | 5 | S?
===================================================================
2 ] 2 | 14 | 62 | 254 | 1022 | 4^S - 2
----+--------+----------+------------+-----------+------+----------
3 ] 12 | 363 | 9840 | 265719 | (27^S - 3)/2
----+--------+----------+------------+-----------+-----------------
4 ] 28 | 7672 | 1965558 | 503184885 |
----+--------+----------+------------+-----------+
5 ] 181 | 571865 | 1787099985 |
----+--------+----------+------------+
6 ] 426 | 19968756 |
----+--------+----------+
7 ] 3930 | (≥ 2^31) |
----+--------+----------+
8 ] 8184 |
----+--------+
9 ] 102780 |
----+--------+
10 ] 199990 |
----+--------+
Я не вижу никаких шаблонов.
В качестве альтернативы, если наклейка начинается с 0 вместо 1,
N\S ] 1 | 2 | 3 | 4 | 5 | S?
======================================================================
2 ] 4 | 20 | 84 | 340 | 1364 | (4^S-1)*4/3
----+---------+----------+------------+-----------+------+------------
3 ] 12 | 363 | 9840 | 265719 | (27^S - 3)/2
----+---------+----------+------------+-----------+-------------------
4 ] 84 | 7764 | 1965652 | 503184980 |
----+---------+----------+------------+-----------+
5 ] 182 | 571875 | 1787100182 |
----+---------+----------+------------+
6 ] 1728 | 19970496 |
----+---------+----------+
7 ] 3931 | (≥ 2^31) |
----+---------+----------+
8 ] 49152 |
----+---------+
9 ] 102789 |
----+---------+
10 ] 1600000 |
----+---------+
Пусть предположим, что он на первый раз накрывает "1" , что действительно имеет место для большинства других вычисляемых данных.
Предположим, что мы находимся в базе N и на каждом DVD-диске будут наклейки S на каждую цифру.
На DVD #X будут использоваться только X и times; S "1" наклейки, используемые или нет.
Число используемых "1" наклеек - это просто число "1" в цифрах от 1 до X в расширении базы N.
Таким образом, нам просто нужно найти точку пересечения X & times; S и общее число цифр "1" .
- N = 2: 1,2,4,5,7,9,12,13,15,17,20,22,25,28, 32,33,35,37,40,42,45,48,52,54,57...
- N = 3: 1,1,2,4,5,5,6,6,7,9,10,12,15,17,18,20,21,21,22,22,23,25, 26,26,27,...
- N = 10: 1,1,1,1,1,1,1,1,1,2,2,4,5,6,7, 8,9,10,11,12,12,13,13,13,13,13...
кажется, что для всех этих последовательностей не существует замкнутой последовательности, поэтому необходимы петлевые пропорциональные X-итерации. Цифры могут быть извлечены в течение журнала X, поэтому в принципе алгоритм может закончить в O (X log X) время.
Это не лучше, чем другой алгоритм, но по крайней мере много вычислений можно удалить. Пример кода C:
#include <stdio.h>
static inline int ones_in_digit(int X, int N) {
int res = 0;
while (X) {
if (X % N == 1)
++ res;
X /= N;
}
return res;
}
int main() {
int N, S, X;
printf("Base N? ");
scanf("%d", &N);
printf("Stickers? ");
scanf("%d", &S);
int count_of_1 = 0;
X = 0;
do {
++ X;
count_of_1 += S - ones_in_digit(X, N);
if (X % 10000000 == 0)
printf("%d -> %d\n", X/10000000, count_of_1);
} while (count_of_1 >= 0);
printf("%d\n", X-1);
return 0;
}
Ответ 6
Вот некоторые мысли о верхней границе, продемонстрированные @Tal Weiss:
Первое 21-значное число 10^20,
, в котором мы будем иметь не более 20 * 10^20
наклейки. Каждый последующий DVD будет стоить нам по крайней мере 1 net sticker, поэтому у нас определенно закончится 10^20 + 20 * 10^20
, что равно 21 * 10^20
. Следовательно, это верхняя граница решения. (Не особенно узкая верхняя граница любыми средствами! Но это легко установить).
Обобщая приведенный выше результат на основание b
:
- каждый DVD поставляется с
2b
наклейками
- первый DVD, который стоит нам 1 net sticker, - это номер
b ^ (2b)
, и в этот момент у нас будет не более 2b . b ^ (2b)
наклейки
- поэтому мы определенно закончим
b ^ (2b) + 2b . [b ^ (2b)]
, что равно (2b + 1)[b ^ (2b)]
Так, например, если мы работаем в базе 3, этот расчет дает верхнюю границу 5103; в базе 4, это 589824. Это числа, которые будут намного проще для грубой силы/математически решить с помощью.