Re[2]: Задачке 45+ лет.
От: Erop Россия  
Дата: 27.02.20 09:29
Оценка:
E>Здравствуйте, NovaMind, Вы писали:

NM>>Есть 12 монет.

NM>>Одна отличается по весу.
NM>>За 3(три) взвешиваня найти эту монету и определить тяжелее она или легче.

NM>>Image: Libra2.jpg



E>
  "полная версия кода"
E>COINS_COUNT = 12
E>SCALE_RESULTS = "<=>" # возможные результаты взвешивания

E>ALL_COINS = set(range(1,COINS_COUNT+1))    
E>ALL_STATES = ALL_COINS | {-s for s in ALL_COINS}
E>MAX_SPLIT_SIZE = COINS_COUNT

E>def _get_subsets_sizes( max_size, max_diff, max_sum ) :
E>    """
E>    Генерируем последовательность размеров пар подмножеств, таких, 
E>    что бы каждое следующее можно было получить из какого-то предыдущего
E>    И что бы сначала перебирать группы множеств с меньшей вариативностью. 
E>    """
E>    res = set() # будем строить множество, что бы не париться с повторами, хотя мы и строим каждую пару один раз
E>    for left_size in range(1, max_size+1) : # леове множество всегда не пустое и максимум max_size
E>        for right_size in range( max(0, left_size-max_diff), min(max_size, left_size+max_diff ) + 1 ) :
E>            to_add = left_size, right_size
E>            assert to_add not in res, f"Повторно сгенерировали пару {to_add}" #это для эстетов
E>            # Мы уже итерируем такие значения right_size, что 0 <= right_size <= max_size
E>            # И -max_diff <= right_size - max_size <= max_diff
E>            if sum(to_add) <= max_sum : # Но есть ещё условие на максимальную сумму размеров
E>                res.add( to_add ) 
E>    # множество построено, осталось отсортировать.
E>    return sorted( res, key=lambda x:(sum(x), max(x), x))

E>def _build_start_1_0_trios( items ) :
E>    """
E>    Строим самое первое подмножество подмножеств, в котором в левом по одному, а правое пустое
E>    За одно вычисляем в каждом случае упорядоченный rest, все элементы в котором больше, 
E>    чем элемент в левом подмножестве
E>    """
E>    items = tuple( sorted( set(items) ) ) #делаем уникальный упорядоченный набор элемнтов
E>    return [(items[i:i+1], tuple(), items[i+1:]) for i in range(len(items))]

E>def _gen_add_item_to_left( trios ) :   
E>    """
E>    перечисляет результаты возможного расширения левых подмножества, за счёт остатков из той же тройки
E>    """
E>    for l, r, rest in trios : #для всех троек
E>        used = set( l ) | set( r ) # строим остаток, в котором нет элемнтов из l и r
E>        rest = [c for c in rest if c not in used]
E>        for c in rest :
E>            if c > l[-1] : #l пустым быть не может. Строим только упорядоченные подмножества
E>                yield l+(c,), r, rest

E>def _gen_add_item_to_right( trios ) :
E>    """
E>    перечисляет результаты возможного расширения правых подмножества, за счёт остатков из той же тройки
E>    """
E>    for l, r, rest in trios : #для всех троек
E>        if r : # правое подможество, в отличии от левого, может быть пустым!
E>            used = set( l ) | set( r ) # строим остаток, в котором нет элемнтов из l и r
E>            rest = [c for c in rest if c not in used]
E>            for c in rest :
E>                if c > r[-1] : # Строим только упорядоченные подмножества
E>                    yield l, r+(c,), rest
E>        else : # случай пустого правого подмножества -- некоторые отсечения нельзя выполнить.
E>            used = set( l ) # строим остаток, в котором нет элемнтов из l и r (но оно всё равно пустое)
E>            rest = [c for c in rest if c not in used]
E>            for c in rest :
E>                yield l, (c,), rest

E>def gen_sorted_pairs( items, max_diff, max_size ) :
E>    """
E>    Генерирует множество пар упорядоченных подмножеств items
E>    """
E>    if max_size < 1 : # нечего генерировать
E>        return
E>    trios_1_0 = _build_start_1_0_trios(items)
E>    if max_diff < 1 : # особый случай, когда надо "преодолевать пропасть в два прыжка"
E>        cur_trios = list( _gen_add_item_to_right(trios_1_0) ) #построили тройки для множеств 1х1
E>        for t in cur_trios :
E>            yield (set(t[0]), set(t[1]))
E>        for i in range(2, max_size+1) :
E>            # Прыжок №1. Сортируем, что бы удобнее было ориентироваться
E>            tmp = _gen_add_item_to_left( sorted( cur_trios ) ) # в генерируемых множествах
E>            # Прыжок №2. Выдаём множества по одному, что бы у перебора была возможность отсечься
E>            cur_trios = [] # Но, если, нам потребуется и более поздняя группа, сохраняем выданное только что множество
E>            for t in _gen_add_item_to_right( tmp ):
E>                cur_trios.append( t )
E>                yield (set(t[0]), set(t[1]))
E>    else : # max_diff >= 1 -- можем позволить себе строить подмножества увеличивая их по одному!
E>        cache = { (1,0):trios_1_0 } # в этом словаре будем хранить наборы множеств, которые планируем расширять
E>        for size in _get_subsets_sizes(max_size, max_diff, len(items)) :
E>            left_size, right_size = size
E>            tmp = []
E>            if size in cache :
E>                for t in cache[size] :
E>                    yield (set(t[0]), set(t[1]))
E>            elif (left_size, right_size-1) in cache :
E>                for t in _gen_add_item_to_right( cache[(left_size, right_size-1)] ) :
E>                    yield (set(t[0]), set(t[1]))
E>                    tmp.append(t)
E>                tmp.sort()
E>                cache[size] = tmp
E>            elif (left_size-1, right_size) in cache :
E>                for t in _gen_add_item_to_left( cache[(left_size-1, right_size)] ) :
E>                    yield (set(t[0]), set(t[1]))
E>                    tmp.append(t)
E>                tmp.sort()
E>                cache[size] = tmp
E>            else :
E>                assert False

E>def _scale_all_states( left, right, states ) :
E>    """
E>    разбить множество состояний на случаи < = >
E>    """
E>    res = { "count":None, "vs": (left, right), '<':[], '=':[], '>':[] }
E>    for s in states :
E>        if -s in left or s in right :
E>            res['<'].append( s )
E>        elif s in left or -s in right :
E>            res['>'].append( s )
E>        else :
E>            res['='].append( s )
E>    return res
    
E>def get_scale_strategy( states, max_count, coins_count=None, max_split_size=MAX_SPLIT_SIZE ) :
E>    """
E>    Возвращает стратегию взвешинвания на двухчашесных весах, или None, если стратегии нет
E>    позволяющую найти отличающуюся по весу единственную фальшивую монету в наборе
E>    states -- множество возвожных исходов
E>    max_count - максимальное число взвешиваний
E>    coins_count -- общее число монет. Еслидостоверно нефальшивых сначала нет, то определяется по states
E>    """
E>    if len(states) <= 1 : # нечего решать, возвращаем некий формальный результат
E>        return {"count":0, "vs":(set(),set()), "<":[], "=":list(states), ">":[]} 
    
E>    if len(states) > 3**max_count :
E>        return None #отсекаем, так как это множество состояний за оставшееся число взвешиваний не разделяется
    
E>    unknown = {abs(c) for c in states} # множество монет, про которые пока не ясно, фальшивые ли они
E>    coins_count = coins_count or len( unknown )
E>    for l, r in gen_sorted_pairs(unknown, coins_count - len(unknown), max_split_size) :
E>        scale_res = _scale_all_states( l, r, states )
E>        #if max_count == 3 and len(l) == 4 and len(r) == 4:
E>        #    print( l, r, scale_res )
E>        max_state_len = max( len(scale_res[k]) for k in SCALE_RESULTS )
E>        if max_state_len > 3**(max_count-1) :
E>            continue #отсекаем, так как это множество состояний за оставшееся число взвешиваний не разделяется
E>        if max_state_len <= 1 : # Мы нашли решение за одно взвешивание!!!
E>            scale_res["count"] = 1
E>            return scale_res
E>        #if max( len(scale_res[k]) for k in SCALE_RESULTS ) == 0:
E>        #    continue #Это только эвристика, но правдоподобная!
E>        if max_count <= 1 :
E>            continue # больше ничего сделать нельзя
        
E>        # Итак, с отсечениями покончено, давайте запускать поиск стратегии рекурсивно!
E>        scale_count = 0
E>        for k in SCALE_RESULTS :
E>            substrategy = get_scale_strategy( scale_res[k], max_count-1, coins_count, max_split_size )
E>            if substrategy is None : # разделить не удалось!
E>                scale_res = None
E>                break
E>            scale_res[k] = substrategy
E>            scale_count = max(substrategy["count"], scale_count)
E>        if scale_res : # Нашли решение для всех трёх результатов взвешивания!
E>            scale_res["count"] = scale_count + 1
E>            return scale_res 
E>    return None

E>get_scale_strategy( ALL_STATES, 3 )
E>


E>Теперь можно пробовать решать разные двухчашечные задачи про уникальные монеты.

E>Например, проверить, а можно ли так же найти фальшивую среди 40 монет за 4 взвешивания?
На самом деле решения с 40 монетами за 4 взвешивания, очевидно, не существует. Ясно, что если после первого взвешивания будет равенство, то оставшиеся монеты будут играть в похожую игру — один ход, но + запас заведомо честных монет. Очевидно, что для 14 монет (28) исходов 3 взвешиваниями (27 исходов) она не решается, а для того, что бы получить меньшее число, надо первым ходом взвешивать 14х14. А это, опять по 28 исходов в случае неравенство.

Но легко проверить, например, что с первым ходом 8х8 это ещё решается, что даёт нам 16+12 = 28 фишек.
Кстати, если сделать запрос про второе взвешивание в духе
COINS_COUNT = 13
ALL_COINS = set(range(1,COINS_COUNT+1))    
ALL_STATES = ALL_COINS | {-s for s in ALL_COINS}
get_scale_strategy( ALL_STATES, 3, COINS_COUNT+16, 5 )

может быть окажется, что можно с 16+13 решить 13 -- это же всего 26 состояний, 27 исходов хватит. Вот 14 уже точно никак разрешить не получится.
То есть не выясненным остаётся только вопрос про 13 монет.
К сожалению комп сына сейчас далеко, так что придётся снова без бурт-форса прикинуть. Если первое взвешивание в задаче про 13 + дополнительные заведомо верные монеты будет 4х4, то в случае равенства останется 10 состояний на 9 возможных исходов.
И при неравенстве при взвешивании 5х5 останется 10 состояний. Ergo -- 12 -- точная верхняя оценка для числа монет, про которые ничего не известно, кроме, быть может, дополнительного запаса верных...

То есть единственный потенциал увеличения -- это 16 первых превратить в 18 и т. п.
Но и тут хочется, например, если есть 18 монет, участвовавших в первом неравновесном взвешивании, разбить их на три группы так, что бы 6 осталось на месте, 6 переехали на другую чашку и 6 вообще во втором не участвовали. Тогда за третье взвешивание нам надо будет выбрать из 6 монет-состояний, что не кажется нереальным.
Так что хочется сказать, что на последнее взвешивание можно прийти максимум с 3 состояниями, на предпоследнее с 9, на третье с конца -- 26, то есть
13 * 2 + 12 = 38 монет

1. 13 х 13. Если равенство, получаем 12 монет и 3 взвешивания. Мы это умеем!
Значит надо рассмотреть случай неравенства. Пусть левая чашка (монеты 1-13) тяжелее правой (монеты 14-26) .

2. взвешиваем: 1-5, 14-17 vs 9-13, 18-21 (а 6-8 и 22-26 откладываем в сторону )
При этом монеты бьются на три группы:
> Те, кто остался на той же чашке: 1-5 и 18-21 (9 шт)
< Те, кто переехал на другую чашку: 9-13 и 14-17 (9 шт)
= Те, кого отложили в сторону: 6-8 и 22-26 (8 шт)
В случае равенства получаем повтор ситуации: делим на группы 3, 2, 3 и дальше всё очевидно
Пусть, во втором взвешивании получили результат "правая тяжелее"
значит монета в группе тех, кто переехал (9-17)
Делим её на 3 группы относительно первого взвешивания (можно и относительно второго)
9, 10, 15 vs 14, 11, 12 (13, 16, 17)
> 9, 10, 14
< 11, 12, 15
= 13, 16, 17
и т. д.

Итого, за 4 взвешивания можно разобраться с 26 + 12 = 38 монетой.
С 40 не получится даже с запасом....
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.