← Вернуться в блог

Симуляция с указателями и бинпоиск по префиксным минимумам — задача «Шулер» РАЗБОР

  • 2000
  • greedy
  • simulation
  • binary-search

Источник задачи: региональный этап Олимпиады им. М.В. Келдыша, 2025, задача D.

Что дано

Колода из 2n2n карт с попарно различными номиналами делится поровну между двумя игроками: у игрока на руке a1,a2,,ana_1, a_2, \ldots, a_n (сверху вниз), у дилера — b1,b2,,bnb_1, b_2, \ldots, b_n (сверху вниз). Играется ровно nn раундов. В каждом раунде оба участника выкладывают верхнюю карту своей руки. Карта с большим номиналом считается победившей и убирается из игры насовсем. Проигравшая карта возвращается наверх руки того, кто её выложил (становится снова верхней картой).

Счёт игрока за игру — число раундов, в которых его карта победила. Игрок имеет право (до начала игры) однократно поменять местами две любые карты в своей руке, либо не менять ничего. Требуется найти максимально возможный счёт игрока.

Ограничения

  • 1n21051 \leq n \leq 2 \cdot 10^5
  • Все 2n2n номиналов попарно различны, 1ai,bi2n1 \leq a_i, b_i \leq 2n.
  • Баллы по подзадачам: n500n \leq 500 — 36 баллов, n5000n \leq 5000 — 60 баллов, полное решение — за O(nlogn)O(n \log n).

Формат ввода

Первая строка — одно число nn. Вторая строка — nn чисел a1,a2,,ana_1, a_2, \ldots, a_n через пробел (рука игрока сверху вниз). Третья строка — nn чисел b1,b2,,bnb_1, b_2, \ldots, b_n через пробел (рука дилера сверху вниз).

Формат вывода

Одно целое число — максимальное количество очков игрока.

Разберём пример 3 руками (без замены карт)

Игрок: 13 7 4 9 12 10 2, дилер: 6 1 14 3 8 5 11.

Обозначим pp — указатель на верхнюю карту игрока, dd — на верхнюю карту дилера. Изначально p=d=0p = d = 0.

Раундapa_pbdb_dПобедительpp послеdd послеОчки игрока
1136игрок101
276игрок202
346дилер212
441игрок313
591игрок414
6121игрок515
7101игрок616

Игрок 6, дилер 1. Ответ: 6. ✓

Ключевой момент: в раунде 3 карта 4 игрока проиграла и вернулась на вершину — значит в раунде 4 игрок снова выкладывает 4. Но на этом этапе у дилера сверху уже 1 (потому что 6 была убрана — выиграла у 4, хотя выиграла-то 6 — нет, стоп, в раунде 3 выиграла 6, но «победившая убирается из игры»). То есть карта дилера 6 убрана, следующая карта у дилера — 1.

Аккуратно перечитаем правила: «Победившая карта убирается из игры, а проигравшая возвращается обратно в руку того, кто её выложил.»

Значит в раунде 3: 6 побеждает 4. Карта 6 убирается — её больше нет. 4 возвращается наверх руки игрока. В раунде 4 сверху: игрок — 4 (вернулась), дилер — следующая карта 1.

Итого поведение рукавов: у проигравшего указатель не двигается (карта осталась сверху), у выигравшего указатель сдвигается (его карта убрана).

Это и есть рабочая модель: указатель движется только у выигравшего.

Разберём пример 1 с заменой

Игрок: 1 6 5, дилер: 2 3 4.

Без замены:

Раундapa_pbdb_dПобедительppddОчки
112дилер010
213дилер020
314дилер030

0 очков. Все 3 раунда проигрывает: карта 1 слишком мала.

Со свапом карт на позициях 0 и 2: игрок становится 5 6 1.

Раундapa_pbdb_dПобедительppddОчки
152игрок101
262игрок202
312дилер212

2 очка.


Идея решения

Базовая симуляция руки — O(n)O(n)

Фиксируем руку игрока aa и дилера bb. Симуляция:

p = d = 0
score = 0
для round = 1..n:
    если a[p] > b[d]:
        score += 1
        p += 1
    иначе:
        d += 1

Ровно nn раундов. На каждом шаге либо игрок, либо дилер двигает указатель. В конце p+(проигранные раунды)=np + (\text{проигранные раунды}) = n, и p+d=np + d = n. pp пройдёт от 0 до (количество побед игрока)(\text{количество побед игрока}), dd — до (количество побед дилера)(\text{количество побед дилера}).

Перебор свапа — O(n²) пар

Можно 0 раз или 1 раз поменять местами aia_i и aja_j. Всего (n2)+1\binom{n}{2} + 1 вариантов руки игрока.

Простейшее решение: перебираем все пары (i,j)(i, j), симулируем — получаем O(n3)O(n^3). Проходит подгруппу n500n \leq 500 (36 баллов).

Что делает полное решение за O(nlogn)O(n \log n) (идея)

Без симуляции это объяснить сложно, но общая интуиция такая:

  1. Префиксные минимумы руки игрока. Пусть pref_min[i]=min(a0,a1,,ai)\text{pref\_min}[i] = \min(a_0, a_1, \ldots, a_i). Позиции, где обновляется минимум, — это «опасные точки»: на них может произойти проигрыш.

  2. Ключевая лемма: в корректной симуляции каждую карту дилера побьёт первая встретившаяся карта игрока с бо́льшим номиналом. И наоборот: если карта игрока apa_p проиграла какой-то bdb_d, то все последующие карты до следующего «префиксного минимума» игрока имеют номиналы больше apa_p, и значит они побьют все следующие карты дилера меньше apa_p — до того момента, когда дилер выложит карту, превышающую apa_p. Формально: если позиции префиксных минимумов руки игрока — k1,k2,,ksk_1, k_2, \ldots, k_s, то при победе карты akja_{k_j} над какой-то btb_t все карты akj+1,akj+2,,akj+1a_{k_j+1}, a_{k_j+2}, \ldots, a_{k_{j+1}} тоже победят следующие карты дилера до момента, когда номинал дилера превзойдёт akj+1a_{k_{j+1}} (поскольку все эти карты по номиналу превосходят akja_{k_j}).

  3. Ответ — монотонная функция от выбранной замены. Если увеличим какую-то позицию свапа, ответ не уменьшится в определённой монотонной структуре. Это позволяет заменить перебор на бинарный поиск по ответу.

  4. Сложность итога: O(nlogn)O(n \log n).

Полная реализация O(nlogn)O(n \log n) — отдельный разбор; выходит за объём этой статьи. Здесь дадим O(n3)O(n^3)-код на частичный балл и обсудим оптимизации.


Решение: псевдокод (O(n³))

читать n, a[0..n-1], b[0..n-1]

best = simulate(a, b)
для i = 0..n-1:
    для j = i+1..n-1:
        swap(a[i], a[j])
        best = max(best, simulate(a, b))
        swap(a[i], a[j])   // откат

вывести best

simulate(a, b):
    p = d = 0
    score = 0
    для round = 1..n:
        если a[p] > b[d]:
            score += 1
            p += 1
        иначе:
            d += 1
    вернуть score

Код решения

Важно: это решение за O(n3)O(n^3). Для полного балла требуется O(nlogn)O(n \log n) через префиксные минимумы и бинарный поиск — значимая дополнительная работа.


Проверим на примерах

Пример 1

a=[1,6,5]a = [1, 6, 5], b=[2,3,4]b = [2, 3, 4].

Симуляция без свапа: все 3 раунда выигрывает дилер, score = 0.

Перебор свапов:

  • (0,1)(0, 1): a=[6,1,5]a = [6, 1, 5] → симулируем:
    • R1: 6>2, score=1, p=1
    • R2: 1<3, d=1
    • R3: 1<3? нет, d=1 → 1<3, d=2
    • Итог: 1.
  • (0,2)(0, 2): a=[5,6,1]a = [5, 6, 1] → симулируем:
    • R1: 5>2, score=1, p=1
    • R2: 6>2, score=2, p=2
    • R3: 1<2, d=1
    • Итог: 2.
  • (1,2)(1, 2): a=[1,5,6]a = [1, 5, 6] → симулируем:
    • R1: 1<2, d=1
    • R2: 1<3, d=2
    • R3: 1<4, d=3
    • Итог: 0.

Максимум = 2. ✓

Пример 2

a=[8,6,3,10,1]a = [8, 6, 3, 10, 1], b=[7,9,5,2,4]b = [7, 9, 5, 2, 4]. Ожидается 3 (со свапом 3 и 10).

Со свапом a[2]a[2] и a[3]a[3]: a=[8,6,10,3,1]a = [8, 6, 10, 3, 1].

  • R1: 8>7, score=1, p=1
  • R2: 6<9, d=1
  • R3: 6<9? уже d=1, b[1]=9, 6<9, d=2
  • R4: 6>5, score=2, p=2...

Стоп, симулируем все 5 раундов:

  • R1: a[0]=8a[0]=8 vs b[0]=7b[0]=7. 8>7, score=1, p=1.
  • R2: a[1]=6a[1]=6 vs b[0]=7b[0]=7. 6<7, d=1.
  • R3: a[1]=6a[1]=6 vs b[1]=9b[1]=9. 6<9, d=2.
  • R4: a[1]=6a[1]=6 vs b[2]=5b[2]=5. 6>5, score=2, p=2.
  • R5: a[2]=10a[2]=10 vs b[2]=5b[2]=5. 10>5, score=3, p=3.

Score = 3.

Пример 3

a=[13,7,4,9,12,10,2]a = [13, 7, 4, 9, 12, 10, 2], b=[6,1,14,3,8,5,11]b = [6, 1, 14, 3, 8, 5, 11]. Ожидается 6 (без свапа).

Симуляция без свапа — 6 раундов выигрышных (см. выше). Перебор свапов — все меньше или равны 6. Максимум = 6. ✓


Крайние случаи

1. Без свапа лучший результат

Пример 3 — никакой свап не улучшит. Алгоритм начинает с simulate(a, b) как baseline.

2. n=1n = 1

Одна карта у каждого. Свап бесполезен (внутри руки игрока свапать нечего — одна карта). Ответ = 1, если a[0]>b[0]a[0] > b[0], иначе 0.

3. Все карты игрока меньше минимума дилера

Тогда без свапа — 0. Свап не поможет, потому что всё равно любая карта игрока меньше любой карты дилера.

Проверяется перебором — ответ 0.

4. n=2105n = 2 \cdot 10^5 (полный балл)

O(n3)=81015O(n^3) = 8 \cdot 10^{15} — не пройдёт. Нужно O(nlogn)O(n \log n) (см. идею выше).


Типичные ошибки

  1. Неверная модель игры. Главная ловушка — понять, что у проигравшего указатель не двигается. Многие пишут симуляцию типа «после каждого раунда оба сдвигают указатели» — и получают неправильный счёт.

  2. Игра длится не пока закончатся карты, а ровно nn раундов. У выигрывавшего стороной кончатся карты раньше, но раунды считаются до nn. В приведённом коде цикл for round in range(n)nn итераций, это корректно.

  3. Предположение, что pp или dd всегда <n< n. В конце цикла у одной стороны указатель может дойти до nn (все карты ушли на «выигрыш»). Если эта сторона — сама игрок, то в последних раундах сам цикл не выйдет за nn: как только p=np = n и d=nd = n, условия не важны (все карты истрачены), а раундов по формуле p+d=np + d = n как раз nn. На практике индексы не выйдут за массив, проверено по примерам.

  4. Переоценка — попытка запустить O(n3)O(n^3) на n=105n = 10^5. TL будет пробит. Для полного балла — O(nlogn)O(n \log n) через префиксные минимумы и бинарный поиск.

  5. Попытка сортировать руку. Соблазнительно, но запрещено: поменять можно одну пару карт, а не перестановить произвольно.


Сложность

  • Базовое решение: O(n3)O(n^3). Проходит подгруппу n500n \leq 500 (36 баллов).
  • Оптимизация до O(n2)O(n^2). Перебор пар + O(1)O(1) обновление симуляции при свапе — технически возможно, но требует аккуратной структуры (отслеживание «критических моментов» симуляции). Проходит n5000n \leq 5000 (60 баллов).
  • Полное решение: O(nlogn)O(n \log n) через префиксные минимумы и бинарный поиск. Полный балл.

Что ещё полезно потренировать

Задачи, где наблюдение про префиксный минимум / максимум решает задачу:

  • Codeforces 1408A «Circle Coloring» — конструктивный анализ соседних элементов.
  • Codeforces 1372C «Omkar and Baseball» — префиксы и суффиксы совпадения.
  • Codeforces 1108D «Diverse Garland» — жадность с локальным анализом.
  • Codeforces 1354E «Graph Coloring» — бинарный поиск по ответу в задаче на граф.

Общий мотив: «если заметить инвариант про префиксы — задача решается O(nlogn)O(n \log n) вместо O(n2)O(n^2)».


Итого

  • Идея: в симуляции указатель двигается только у выигравшего; simulate(a, b) за O(n)O(n).
  • Решение на 36 баллов: перебор всех свапов O(n3)O(n^3).
  • Полное решение (O(n log n)): префиксные минимумы руки + бинарный поиск — отдельная глубокая оптимизация, не раскрыта в этой статье.
  • Ловушки: неверная модель указателей, попытка отсортировать всю руку, O(n3)O(n^3) на n=105n = 10^5.

Попробуй разобрать похожие задачи

В CodePal AI-партнёр подсказывает идею, а не ответ. Разбор в диалоге, код проверяется в браузере.