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

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

1780A - Школа и Хаято

Идея: AndreyPavlov
Подготовка: AndreyPavlov
Разбор: AndreyPavlov

Разбор
Решение (Python)
Решение (C++)

1780B - НОД разбиение

Идея: 74TrAkToR
Подготовка: qualdoom
Разбор: qualdoom

Разбор
Решение (Python)
Решение (С++)

1780D - Битовая угадайка

Идея: AndreyPavlov
Подготовка: qualdoom, AndreyPavlov
Разбор: qualdoom, AndreyPavlov

Разбор
Решение (Python)
Решение (С++)

1780E - Джоске и полный граф

Идея: IzhtskiyTimofey
Подготовка: IzhtskiyTimofey
Разбор: IzhtskiyTimofey

Разбор
Решение (Python)
Решение (С++)

1780F - Три стула

Идея: AndreyPavlov
Подготовка: AndreyPavlov
Разбор: qualdoom

Разбор
Решение (С++)

1780G - Вкусный десерт

Идея: IzhtskiyTimofey
Подготовка: IzhtskiyTimofey, AndreyPavlov
Разбор: IzhtskiyTimofey, AndreyPavlov

Решение
Решение (t4m0fey)
Решение (AndreyPavlov)

Полный текст и комментарии »

Разбор задач Codeforces Round 846 (Div. 2)
  • Проголосовать: нравится
  • +348
  • Проголосовать: не нравится

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

Привет, Codeforces!

IzhtskiyTimofey, qualdoom и я рады пригласить вас на наш Codeforces Round 846 (Div. 2), который пройдёт в 25.01.2023 17:35 (Московское время).

Раунд будет рейтинговым для всех участников, чей рейтинг будет ниже 0x834 (то есть 2100). Участники с более высоким рейтингом могут принять участие в раунде неофициально.

Вам будет дано целых 7 задач и 2 часа, чтобы их решить.

Одна из задач будет интерактивной. Обязательно прочитайте этот блог и ознакомьтесь с этими типами задач перед раундом!

Хочу искренне поблагодарить всех, кто оказал бесценную помощь в подготовке раунда и сделал его в разы лучше:

Это наш первый официальный раунд на Codeforces. Мы искренне надеемся на ваше участие. Также мы надеемся, что предложенные задачи вам понравятся!

Разбалловка будет объявлена ближе к началу раунда.

Желаем удачи и приятного времяпровождения! Увидимся на раунде!

UPD: Разбалловка: $$$500-1000-1250-1500-1750-2000-2500$$$

UPD: Нам очень жаль, но раунд признан нерейтинговым. Приносим свои извинения — это наша ошибка.

UPD: Разбор и комментарий по поводу задачи С. Ещё раз приносим извинения за предоставленные неудобства.

Полный текст и комментарии »

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

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

Предистория

Недавно писал решение на свою задачу (ниже приложен мэшап, где она находится). Обычная задача на паросочетания, и в полученном графе очень быстро искалось максимальное паросочетание, что показалось мне странным (а также я не знаю как строго доказывать такую скорость) и поэтому я решил написать этот пост.

Краткая формулировка задачи

Дан ориентированный граф на $$$n$$$ вершинах и $$$m$$$ ребрах, нужно разбить граф на минимальное количество путей. Особенностью данного графа является то, что его диаметр $$$^\dagger$$$ можно оценить как $$$O(log(n))$$$.

$$$^\dagger$$$ Диаметром графа называется максимальное кратчайшее расстояние среди всех пар вершин.

Решение

Очевидно, что данная задача является учебной — нужно дублировать вершины и провести ребра графа в новом двудольном графе. Ответом будет $$$n - x$$$, где $$$x$$$ — максимальное паросочетание в получившемся двудольном графе.

Я приложу свой код алгоритма Куна, который содержит несколько оптимизаций.

int used[N];
int mt[N], timer = 1;
vector <int> g[N];
bool dfs (int u) {
    if (used[u] == timer) return false;
    used[u] = timer;
    for (int v: g[u]) {
        if (mt[v] == -1) {
            mt[v] = u;
            return true;
        }
    }
    for (int v: g[u]) {
        if (dfs(mt[v])) {
            mt[v] = u;
            return true;
        }
    }
    return false;
}
// ...
int main () {
    // ...
    for (int i = 0; i < n; ++i) {
        dfs(i);
        timer++;
    }
    // ...
}

Оптимизации:

  • Хранение в массиве used значение timer'а, а не зануление used'ов каждый раз

  • При запуске сначала ищем свободную вершину во второй доле, а потом ищем удлиняющую цепочку.

Почему так быстро?

Казалось бы, при ограничениях $$$n = 10^5, m = 10^6$$$ Кун будет работать $$$O(n \cdot m)$$$, что очень долго. Но на реальном графе Кун работает ~$$$100$$$ мс.

Лично я не знаю, как доказывать такую асимптотику, но моя гипотеза, что у Куна есть ограничение на количество проделанных операций. И данное ограничение можно оценить как $$$O((n + m) \cdot d)$$$, где $$$d$$$ — диаметр графа.

Вот ссылка на мэшап, где можно протестировать Куна.

Интересно услышать ваше мнение насчёт данной гипотезы.

Полный текст и комментарии »

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

Автор AndreyPavlov, история, 2 года назад, По-английски

Problem

When you learn about Heavy Light Decomposition tree problems take on new meaning. But writing HLD every tree problem with queries so boring. In this post, I will show some problems (as well as ideas for solving them) in which you can write HLD, but with a little thought, the solution becomes easier.

Modifications and receiving at the point

Task: A tree is given, as well as requests to add in a subtree and on the path to the root, as well as to find out the value at the top.

First idea — HLD! But stop, we only need the value at the point, maybe can easier? Yes, we will solve task in offline mode.

Idea: How to find out what changes occurred at the top at time $$$t$$$? Need to store some structure, for example, segment tree and answer for query in time $$$t$$$ is sum on prefix $$$t$$$. Now we have add in subtree and on the path to the root. For subtere adding we will remember for each vertex that in its subtree we need to add the number $$$x$$$ and the query time. Also for path adding we will remember this. Now we run the dfs on this tree with next algorithm:

  1. Checking all subtree addings and remember in segment tree with adding in point $$$t$$$ value $$$x$$$
  2. Go to the childs with dfs
  3. Checking all paths to the root adding and remember as well as in 1
  4. Get the answer on all queries with sum on prefix $$$t$$$
  5. Remove all subtree addings (add $$$-x$$$ on prefix $$$t$$$)

DFS implementation:

// ...
void dfs (int u, int p) {
    for (auto [t, x]: subtree_query[u]) {
        segment_tree.upd(t, x);
    }
    for (int child: graph[u]) {
        if (child != p) {        
            dfs(child, u);
        }
    }
    for (auto [x, t]: path_root_query[u]) {
        segment_tree.upd(t, x);
    }
    for (auto t: query[u]) {
        answer[t] = segment_tree.get(0, t);
    }
    for (auto [t, x]: subtree_query[u]) {
        segment_tree.upd(t, -x);
    }
}
// ...

NOTE: instead of a segment tree, you can use a fenwick tree

Asymptotics is $$$O(n + q \cdot log{q})$$$

Adding on path, get the value at the point

Task: Given a tree and given queries to add x on the path and get the value at the point. Sounds like a normal task on HLD, because we need to add on path of two vertex. But here you can do without hld.

Idea: Find the LCA of 2 vertex, call it $$$l$$$. We will be again work in offline mode, we know of query his time $$$t$$$. First we solve the problem, if first there are change requests and then get requests at the point. We will make next trick — we create a new array let's call him $$$a$$$ add $$$-1$$$ to $$$a_l$$$ and parent of $$$l$$$, add $$$1$$$ to $$$a_u$$$ and $$$a_v$$$. Next we run dfs on this tree and for every vertex $$$u$$$ count $$$\sum{a_i}$$$ where $$$i$$$ is vertex in subtree of $$$u$$$. Note that this sum will be equal to the number that will be written at the top after all requests. How to solve our originially problem? Let's add a segment tree for time $$$t$$$ just like you did in the previous task.

DFS implementation:

// ...
void dfs (int u) {
    for (auto [t, x]: subtree_query[u]) {
        segment_tree.upd(t, x);
    }
    for (int child: graph[u]) {
        if (child != p[u]) {        
            dfs(child);
        }
    }
    for (auto t: query[u]) {
        answer[t] = segment_tree.get(0, t);
    }
}
// ...
void add (int u, int v, int t) {
    int l = lca(u, v);
    subtree_query[l].push_back({t, -1});
    subtree_query[p[l]].push_back({t, -1});
    subtree_query[u].push_back({t, 1});
    subtree_query[v].push_back({t, 1});
}

Note: we also may change segment tree on fenwick tree

Asymptotics $$$O(n + q \cdot log{q})$$$

Tasks

D. Water Tree — assign $$$1$$$ on subtree, $$$0$$$ on path to root and get the value in point.

C. Propagating tree — add alternating sum to subtree and get the value at point.

E. Vasya and a Tree — add $$$x$$$ to the subtree $$$k$$$ level and output all values after.

A. Max Flow — add $$$1$$$ on path and output max vertex value

Полный текст и комментарии »

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