Блог пользователя tiom4eg

Автор tiom4eg, история, 15 месяцев назад, По-русски

Привет, Codeforces!

Вчера закончился длинный тур Открытой олимпиады этого года, который проходил с 21.11 по 15.01. В нём было много интересных задач, которые мне хотелось бы разобрать и обсудить в этом посте.

Я постараюсь описать свои мысли в ходе решения как можно подробнее, но я не гарантирую, что начинающим спортпрогерам всё будет понятно ;)

Собственно, ниже мои решения + реализации (прощу прощения, если код трудночитаем):

А на 100 баллов
B на 76 (100?) баллов
C на 47 баллов
D на 100 баллов
E на 67 (100?) баллов
F на 76 (100?) баллов
G на 100 баллов
H на 79 (100?) баллов
I на 82 балла + некоторые идеи на 100 баллов

Спасибо за внимание!

  • Проголосовать: нравится
  • +114
  • Проголосовать: не нравится

»
15 месяцев назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

Автокомментарий: текст был обновлен пользователем tiom4eg (предыдущая версия, новая версия, сравнить).

»
15 месяцев назад, # |
  Проголосовать: нравится +10 Проголосовать: не нравится

Жесть, крут)

»
15 месяцев назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

В C на 47 заходил квадратик от входящих на выходящие))))

»
15 месяцев назад, # |
Rev. 2   Проголосовать: нравится +8 Проголосовать: не нравится

Dшка, более лёгкая идея: Делаем сет отрезков, обрабатываем слева направо запросы. Если передвигаемся из I в I + 1, удаляем все отрезки с r = I, добавляем все с l = I + 1 Реализация глинистая(

C:пишем BFS: (расскажу решение для цепочек и циклов, для деревьев обобщается) рассмотрим для какой-то цепочки первую вершину (пусть v), которую мы на ней отметим (и будем на каждой цепочке поддерживать все линейные функции, через которые будем пересчитываться) тогда заметим, что если мы обновим кратчайшие расстояния через вершинку v, то оптимальной будет вершинка, наиболее близкая к ней: пересчитаем эту вершинку через линейные функции, принадлежащие цепочке, и добавим ее в priority_queue пересчитваем и добавляем на отрезках с помощью ДО + Li-Chao (не писал, было очень лень, мб не работает)

  • »
    »
    15 месяцев назад, # ^ |
    Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится

    В C есть только одна нетривиальная конструкция — это цикл (возможно, вырожденный), в каждую вершину которого приходит несколько (возможно, ноль) деревьев. Чтобы побороть цикл, можно мысленно разрезать по ребру и удвоить его, в одной половине учитывая все подвешенные деревья, а в другой — нет. Ну и дальше действительно делаем ДО/HLD + Ли-чао.

    Это должно работать, потому что нам не нужно ходить по циклу больше одного раза, ну и полученная конструкция также будет деревом :)

    • »
      »
      »
      15 месяцев назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится

      HLD + Li-Chao будет $$$n \log^3n$$$ при $$$n \leq 500 000$$$?

      • »
        »
        »
        »
        15 месяцев назад, # ^ |
          Проголосовать: нравится 0 Проголосовать: не нравится

        Ну если строить Li-Chao на каждом из тяжелых путей, то должно быть $$$O(n log^2 n)$$$, Qwerty1232 вроде бы знает какой-то хак, чтобы получить $$$O(n log n)$$$. Лог в кубе, конечно, не должен заходить.

        • »
          »
          »
          »
          »
          15 месяцев назад, # ^ |
            Проголосовать: нравится +3 Проголосовать: не нравится

          Как сделать $$$O(n\log^2n)$$$ с Li-Chao?

          В моей реализации подход HLD + Li-Chao был $$$O(n \log^3n)$$$ с малой константой, потому что вставлять линейные функции нужно не на всём Li-Chao, а на суффиксе, и Li-Chao вроде бы это умеет делать только за $$$O(\log^2 n)$$$. При релаксации по ребру нам нужно добавить прямую в $$$O(\log n)$$$ тяжелых путей.

          Я знаю решения за:

          • $$$O(n\log^3n)$$$ с HLD + Li-Chao
          • $$$O(n\log^2n)$$$/$$$O(n\log n\log\log n)$$$ с Dynamic Convex Hull на std::set
          • кажется $$$O(n\log n)$$$ с DCH на декартовом (больно)
          • кажется $$$O(n\log n)$$$ на бинарной куче (больнее)
          • $$$O(n\log n)$$$ на кастомной "куче" с идеей хранения множества в виде $$$\log n$$$ отдельных кусков экспоненциально растущих размеров

          Второе уже выглядело впихуемо, возможно даже первое, если не заставлять Li-Chao поддерживать минимум.

»
15 месяцев назад, # |
Rev. 3   Проголосовать: нравится +13 Проголосовать: не нравится

Для G у меня было решение попроще в плане написания и понятности.

Скажем, что изначально ответ — это строка вида $$$()()()...()()$$$. Затем, заметим полезный факт — если есть закрывающая скобка с индексом $$$i$$$, то её можно заменить на открытую скобку с индексом $$$j > i$$$ и ПСП останется ПСП. Теперь будем идти по строке справа налево и применять следующий жадный алгоритм: поддерживаем сет из открытых скобок (их стоимостей), если встретилась закрытая скобка — посмотрим на минимум в сете, если заменить его выгодно — заменяем. Итого получится оптимальная строка.

Также приведу код:

string s(n, '(');
set <pair<int, int>> st;
for (int i = 1; i < n; i += 2) {
    s[i] = ')';
}
for (int i = n - 1; i >= 0; --i) {
    if (s[i] == ')') {
        if (!st.empty() && st.begin()->first < a[i]) {
            swap(s[i], s[st.begin()->second]);
            st.erase(st.begin());
        }
    }
    if (s[i] == '(') {
        st.insert({a[i], i});
    }
}
»
15 месяцев назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

а будет дорешка?

»
15 месяцев назад, # |
  Проголосовать: нравится +6 Проголосовать: не нравится

Для задачи H есть решение, которое можно оптимизировать для ответа на запрос за O(1).

https://pastebin.com/TPYea9hF

»
15 месяцев назад, # |
Rev. 2   Проголосовать: нравится +21 Проголосовать: не нравится

Задача C. Доставка еды

Общая идея -- Дейкстра + структура данных для обновления времен посещения перекрёстков по цепочке продолжений дорог.

1а. HLD + Li-Chao, неоптимизированный n log^3
1б. HLD + Li-Chao, оптимизация, улучшающая константу
2а. Dynamic Convex Hull на std::set, n log^2
2а+. Dynamic Convex Hull на std::set, n log^2 / loglog
2б. Dynamic Convex Hull на структуре данных с быстрым merge, n log
»
15 месяцев назад, # |
  Проголосовать: нравится +31 Проголосовать: не нравится

Задача I. Гладкие числа

Общая идея -- meet-in-the-middle
»
15 месяцев назад, # |
Rev. 3   Проголосовать: нравится +8 Проголосовать: не нравится

Статическое (на 76 баллов), но детерминированное решение F.

Определим p[i] := минимальная позиция j > i такая, что подстрока [i..j] сбалансирована. Эту величину можно посчитать одним проходом справа налево с помощью простой динамики. Теперь из каждого i проведём ребро в p[i] + 1. Получим лес.

Как отвечать на запрос [l; r]? Для этого нужно просто проверить, что вершина r + 1 является родителем вершины l. Это можно делать, предподсчитав бинапы или tin и tout для каждой вершины dfs'ом.

»
15 месяцев назад, # |
Rev. 3   Проголосовать: нравится +6 Проголосовать: не нравится

Приведу другое решение Dшки с Segment Tree Beats.

Для начала нужно понять, что ответ для позиции $$$i$$$ можно посчитать так:

присваиваем $$$ans[i]=m,$$$ идем по всем запросам по порядку $$$j=0, j \to m,$$$ если позиция $$$i$$$ входит в границы $$$j$$$-го запроса и если его тип не равен типу прошлого запроса, покрывающего позицию $$$i,$$$ то прибавляем такие величины: если $$$type[j] = ClearSnow,$$$ то $$$ans[i]$$$+= $$$m-j,$$$ иначе если $$$type[j] = AddSnow,$$$ то $$$ans[i]$$$-= $$$m-j.$$$

Теперь перенесем эту идею на дерево отрезков:

Мы можем делать такие операции для отрезка позиций, только если у каждой из них прошлый запрос был одинаковый, то есть если на каждой из них сейчас лежит снег, либо не лежит ни на одной.

Обозначим $$$CntSnow$$$ — количество покрытых снегом позиций на отрезке, $$$Len$$$ — длина отрезка. Будем спускаться по дереву, пока $$$CntSnow ≠ 0$$$ $$$or$$$ $$$CntSnow ≠ Len,$$$ далее можно обновить значения как нам нужно.

Доказательство асимптотики $$$O(mlogn)$$$:

Каждый запрос к ДО создает не больше $$$O(logn)$$$ вершин дерева со свойством $$$CntSnow ≠ 0$$$ $$$or$$$ $$$CntSnow ≠ Len,$$$ при этом запрос убирает это свойство для всех вершин, в которые спуск был совершён из-за расширенного $$$Tag$$$_$$$Condition.$$$ Таким образом мы можем совершить не больше $$$O(mlogn)$$$ дополнительных спусков за $$$m$$$ запросов.

»
15 месяцев назад, # |
Rev. 2   Проголосовать: нравится +6 Проголосовать: не нравится

Для задачи Е есть более просто решение(без корневой декомпозиции):

Для начала сожмем все числа.
Посчитаем массив pos[x] — массив позиций на которых встречается число x.

Теперь обрабатываем запросы и запоминаем ответ для каждой упорядоченной пары.
Запоминать ответ для пар можно при помощи такой структуры как std::map.

Отвечаем на запрос {x, y}:
1. У нас эта пара уже была посчитана. Тогда просто выводим ответ.
2. У нас эта пара не была посчитана. Тогда считаем ответ, причем перебираем позиции того числа, которое встречается реже(определить это можно при помощи массива pos). Запоминаем ответ.

Это решение у меня зашло на 100 баллов, но я не могу показать асимптотику алгоритма. Буду рад, если кто-нибудь подскажет.
Код решения: https://pastebin.com/hi5u3ntc

  • »
    »
    15 месяцев назад, # ^ |
      Проголосовать: нравится +9 Проголосовать: не нравится

    $$$O((m + n) \sqrt n \log n)$$$, примерно, ведь если размер маленькой меньше, чем $$$\sqrt n$$$, то мы отработаем за его размер * log, ну а всего таких запросов не больше m.

    Если размер маленькой больше корня, то суммарно это не больше чем $$$O(n \sqrt n \log n)$$$, ведь для каждой такой (у которой размер больше корня) есть всего $$$O(\sqrt n)$$$ пар.