Загадка, которая игнорирует метод решения "в лоб"?

Я купил пустой DVD для записывания моей любимой телепередачи. Это шло с 20 этикетками цифры. 2 из каждого '0 '-'9'.
Я думал, что это будет хорошая идея численно маркировать мой новый набор DVD. Я записал на ленту '1' этикетка на моем первом зарегистрированном DVD и поместил 19 оставшихся этикеток в секцию.
На следующий день я купил другой пустой DVD (получающий 20 новых этикеток с ним) и после записи шоу, которое я маркировал им '2'.
И затем я начал задаваться вопросом: когда этикетки закончатся, и я больше не буду мочь маркировать DVD?
Несколько строк Python, нет?

Можно ли предоставить код, который решает эту проблему с разумным временем выполнения?

Править: Грубая сила просто займет слишком много времени работать. Улучшите свой алгоритм, таким образом, Ваш код даст правильный ответ через, скажем, минуту?

Дополнительный кредит: Что, если DVD шли с 3 этикетками каждой цифры?

13
задан Tal Weiss 24 July 2010 в 19:22
поделиться

6 ответов

Совершенно новое решение. В 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
ответ дан 1 December 2019 в 23:13
поделиться

Результаты для любой базы 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 и будет S новых наклеек на цифру для каждого DVD.

На DVD №X будут полностью наклейки размером X × S «1», независимо от того, использовались они или нет.

Количество используемых наклеек «1» - это просто количество «1» в цифрах от 1 до X в расширении с основанием N.

Таким образом, нам просто нужно найти точку пересечения X × S и общее количество цифр «1».

, похоже, не существует замкнутого для всех этих последовательностей, поэтому цикл, пропорциональный X итераций необходимо. Цифры могут быть извлечены за время log 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;
}
1
ответ дан 1 December 2019 в 23:13
поделиться

вот быстрый и грязный сценарий python:

#!/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}
2
ответ дан 1 December 2019 в 23:13
поделиться

Вот некоторые мысли по поводу верхней границы, продемонстрированной @Tal Weiss:

Первое 21-значное число равно 10^20, в этот момент у нас будет максимум 20 * 10^20 наклеек. Каждый последующий DVD будет стоить нам как минимум 1 чистую наклейку, так что мы определенно исчерпаем 10^20 + 20 * 10^20, что равно 21 * 10^20. Таким образом, это верхняя граница решения. (Ни в коем случае не особенно жесткая верхняя граница! Но ее легко установить).

Обобщая вышеприведенный результат на базу b:

  • каждый DVD поставляется с 2b наклейками
  • первый DVD, который стоит нам 1 чистую наклейку - это номер b ^ (2b), и в этот момент у нас будет максимум 2b . b ^ (2b) наклеек
  • так что мы обязательно исчерпаем их на b ^ (2b) + 2b . [b ^ (2b)], что равно (2b + 1)[b ^ (2b)]

Так, например, если мы работаем по основанию 3, то этот расчет дает верхнюю границу 5103; по основанию 4 - 589824. Это числа, которые будет гораздо проще перебрать/математически решить.

1
ответ дан 1 December 2019 в 23:13
поделиться

Это старое решение, совершенно новое решение в 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's 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:])
7
ответ дан 1 December 2019 в 23:13
поделиться

Вот доказательство того, что решение существует:

Предполагая, что вы когда-нибудь доберетесь до 21 цифры, вы начнете терять наклейку с каждым DVD, который вы покупаете и метите ((+20) + (-21)).
Не имеет значения, сколько наклеек вы накопили до этого момента. С этого момента все идет под откос для вашего тайника, и вы в конечном итоге иссякнете.

6
ответ дан 1 December 2019 в 23:13
поделиться
Другие вопросы по тегам:

Похожие вопросы: