Головоломка, которая бросает вызов подходу грубой силы? - PullRequest
13 голосов
/ 24 июля 2010

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

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

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

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

Ответы [ 6 ]

7 голосов
/ 24 июля 2010

Это старое решение , совершенно новое решение в 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:])
6 голосов
/ 24 июля 2010

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

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

2 голосов
/ 25 июля 2010

Абсолютно новое решение. В 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 голосов
/ 24 июля 2010

вот быстрый и грязный скрипт на 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}
1 голос
/ 24 июля 2010

Вот некоторые мысли о верхней границе, продемонстрированные @ 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 голос
/ 24 июля 2010

Результаты для любой базовой 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».

не представляется замкнутым для всех этих последовательностей, поэтому петлевые пропорциональные 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;
}
...