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

Автор Zlobober, 8 лет назад, перевод, По-русски

Всем привет!

Приглашаю вас поучаствовать во втором раунде Яндекс Алгоритма, который состоится завтра в 21:00 по Москве. Этот раунд был подготовлен мной с огромной помощью GlebsHP. Хочу поблагодарить Chmel_Tolstiy, snarknews и Gassa за их поддержку и советы во время подготовки, а также всех сотрудников компании Яндекс, которые тестировали набор задач.

Good luck and have fun!

Ссылка на вход в контест появится здесь, как только я сам её узнаю :)

UPD: как мне подсказали, войти в контест можно будет здесь: https://contest.yandex.ru/algorithm2016/contest/2540/enter/

UPD2: Спасибо за участие, надеюсь, вам понравились задачи! Результаты будут доступны в ближайшем времени. Я бы хотел запостить разбор, но он что-то не собирается на Codeforces, и я сейчас пытаюсь побороть эту проблему.

UPD3: Поздравляем победителей:

  1. jqdai0815
  2. eatmore
  3. rng_58
  4. jcvb
  5. KAN

Опубликована предварительная pdf-версия разбора: http://codeforces.net/blog/entry/45354. Продолжая традицию сопровождать разбор интересными вопросами, связанными с задачами, я подобрал несколько и в этот раз. Приглашаю вас над ними подумать.

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

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

There is no way to participate for those who didn't took part in previous rounds, am I right?:(

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

    Unfortunately there is no way to participate during the contest for those who didn't qualified.

    Though after each round is held, it becomes available for participation on http://contest.yandex.ru. We will also, most probably, publish archives containing test data and all materials of all rounds in the nearest future.

    • »
      »
      »
      8 лет назад, # ^ |
        Проголосовать: нравится -40 Проголосовать: не нравится

      Why is there no mirrors on CF? I don't know how difficult to make mirror but think it should be simpler than make new round from scratch (in this case CF get all problems for free).

»
8 лет назад, # |
  Проголосовать: нравится +25 Проголосовать: не нравится

Darn, it overlaps with first match of Euro : /.

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

    Yep, I'm choosing a nice bar with live football to conduct the contest from there right now :)

    Sorry for that, I couldn't do anything with the time of round.

    I can send you messages about goals via testing system :D

»
8 лет назад, # |
  Проголосовать: нравится +13 Проголосовать: не нравится

Don't forget to participate!

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

Была ли регистрация к раунду ? Было бы лучше если её открыли во время контеста.

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

    Её не было и не будет. Регистрация была на участие в квалификационных раундах, которые уже прошли.

»
8 лет назад, # |
  Проголосовать: нравится +9 Проголосовать: не нравится

Thanks for making the system fast and stable this time. :)

How to solve C?

»
8 лет назад, # |
  Проголосовать: нравится +5 Проголосовать: не нравится

Как решать шизофренические суммы?

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

    Ответ — это количество путей по сетке идущих вверх или вправо до какой-то точки вида Ax + By = C. Динамика dp[C] = dp[C - A] + dp[C - B], свертываем в матрицы.

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

    Предполагаю, что можно также предположить, что мы считаем количество разбиений отрезка длины C на отрезки длины A и B и запустить рекурсию с запоминанием по параметру C, перебирая, каким отрезком занята центральная клеточка и как расположен этот отрезок. Вроде должно обработаться порядка состояний. Правда, у меня пока WA 25.

»
8 лет назад, # |
  Проголосовать: нравится +5 Проголосовать: не нравится

Я решил A, B, Е, не решив C и D.

Что со мной не так?

И расскажите пожалуйста как решать C =(

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

    я писал ДП

    dp[i][j] — ответ если i-ое число равно j. Идём с конца и для каждой ячейки поддерживаем то сколько нам надо ещё оплатить. Ответ очевидно в dp[1][1]. размер dp 20 на max(a[i]) т.к. иметь купюру большую чем стоимость любого товара нам не нужно, а 20, т.к. одинаковые купюры нам не нужны, т.к. любую и так можно брать сколько угодно раз, a минимальное число для X, которое кратно Х это 2*Х, то есть купюр максимум будет log2(max(a[i])). дальше есть свойство что n/1 + n/2 + n/3 + n/4 + ... = nlog(n). из этого выходит асимптоматика log2(max(a[i])) * max(a[i]) * log2(max(a[i])) = log(max(a[i]))^2 * max(a[i]).

    CODE

»
8 лет назад, # |
  Проголосовать: нравится +3 Проголосовать: не нравится

How to solve B?

  • »
    »
    8 лет назад, # ^ |
    Rev. 6   Проголосовать: нравится +7 Проголосовать: не нравится

    Write L and R in binary form.

    Now the problem can be solved independently for each bit: you need to calculate for each bit how much there will be nums in [l, r] with this bit set and unset. And then select the bit value, based on this info.

    How to count how much nums in [l, r] with such bit set? Traditional trick: it is count in [0, r] — count in [0, L — 1].

    So how much nums in [0, r] with i'th bit set? let r = A C B (where A value of highest bits, c value of i's bit, B value of lower bits) It is A * 2^i + c * (B + 1).

    (At first consider all numbers which are less than A * 2^(i + 1), and then the all other)

    In first group exactly half will be good, and in second none will be good if i'th bit unset (c == 0), and all numbers in [A C {zeros}; A C B] otherwise.

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

      How did you calculate the number of nums in [L, R] with this bit set and unset?

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

        I've written second part and updated comment.

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

        I dont know how he did it, but I think it can be done easily using DP on digits with state DP(bit, lower, larger, tofix) which is O(323·2·2·) where bit is the bit we are currently on, lower is {0, 1} denoting whether any previous bit is lesser than the bit in R, and larger is {0, 1} denoting whether some bit has been strictly larger than that of L. tofix denotes the bit we are fixing to be true. After that, it can be solved by using a lot of if-elses. I couldn't get it accepted in contest(WA on #10), but I dont think the DP would be wrong. My transition probably has bugs :/

        Is there anything wrong with this approach?

        UPD: Damn, extremely overkill :/

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

      Got this strategy but couldn't count how many times it occurs. Can you explain you method to count how many times each bit is set in range [l,r].

»
8 лет назад, # |
  Проголосовать: нравится +3 Проголосовать: не нравится

For Problem B, finding the number of integers in [L, R] with ith bit set could be used to decide if the required number should have ith bit set or not, to minimize the hamming distance.

So, how do we find number of integers in [L, R] with ith bit set?

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

    Find count of '1' for every position for all numbers from 0 to n;

    It's linear in number of digits.

    Then you can do F(r) — F(l-1) to find what you need.

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

    We can calculate the number of integers in [0, R] with i-th bit set using the following fact: if we consider consecutive blocks of size 2i + 1, the first half of integers has i-th bit set to zero, and the second half with i-th bit set to one. Therefore, one can come up with simple formula.

»
8 лет назад, # |
  Проголосовать: нравится +50 Проголосовать: не нравится

That awkward moment when your solution to D fails because of

	if (c == 0)
	{
		cout << 0 << endl;
		return 0;
	}
  • »
    »
    8 лет назад, # ^ |
      Проголосовать: нравится +38 Проголосовать: не нравится

    That awkward moment when the bug in your code is that you forget to return your answer...

    int query(int l, int r, int x) {
        int res = 0;
        for (l += n, r += n; l < r; l /= 2, r /= 2) {
            if (l & 1) {
                res += t[l].end() - upper_bound(t[l].begin(), t[l].end(), x);
                ++l;
            }
            if (r & 1) {
                --r;
                res += t[r].end() - upper_bound(t[r].begin(), t[r].end(), x);
            }
        }
    }
    
    • »
      »
      »
      8 лет назад, # ^ |
        Проголосовать: нравится -17 Проголосовать: не нравится

      an awkward moment when you haven't finished reading A, wrote solution in 5:24, sent it blindly and it obviously failed because it never outputs -1 xD

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

      That's why keep warnings flag on always, same thing happened with me 1 year ago and your's is still int returning function mine was bool returning which doesn't show warning also.

»
8 лет назад, # |
  Проголосовать: нравится -13 Проголосовать: не нравится

У соревнования какая-то очень странная система оценки: фиолетовый, решивший 2 раунда по 3 задачи, но не смогший написать 3-й, проиграет из-за штрафа синему, который нашел время прорешать все. Не очень понятно, зачем тогда вообще делать целых 3 раунда.

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

    Эта система плоха только тем, что требует участия во всех раундах — если меньше, то это риск участника. Т.е. затраты времени. Хороша тем, что используется усреднение по трём раундам, целых 15 задач, решённых в три разных захода. Поэтому любые случайные факторы — плохой день / плохая задача / застрял на одной и не решил и прочее — нивелируются. Меньше случайностей => достоверней результат соревнования.

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

      А еще теоретически можно попасть в топ25 в каждом из раундов, и при этом не попасть в топ25 по сумме, если писать все время усредненно :D

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

    Видимо, чтобы понизить элемент случайности, который всегда есть у одного отдельно взятого раунда. Если хочется проходить в финал — стоит всё-таки уделить время на все три раунда. А для футболки, вроде бы, 2 раунда по 3 задачи должно будет хватать.

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

How to solve D?

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

    ans(C) = ans(C - A) + ans(C - B), ans(0) = 1 and use fast matrix exponentation.

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

    Sum of required f(x,y) is sum of f(x-1,y)+f(x,y-1)

    if Ax+By=C, then A(x-1)+By=C-A, Ax+B(y-1)=C-B

    so you need to calculate answers for C-A and C-B and sum them

    the rest is converting linear recursion to matrix powers

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

      Thanks a lot! It was simple but i turned it into a complicated series of binomial coefficients and could not simplify it :(

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

Как решать Е? были подвижки, но как-то не добил..

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

    Подвешиваем за 1, выписываем эйлеров обход (в обходе приоритеты), строим простое двумерное ДО на эйлеровом обходе (там где сорченный список в ноде).

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

    Запускаем DFS и считаем ответ для каждой ноды, делая такие сдвиги.

    http://pastebin.com/nWPTCRyb

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

    Для одного корня за NlogN решать просто — запустим дфс, будем хранить в ДО/Фенвике сколько есть вершин с определенным значением на пути от корня к текущей вершине, в каждой вершине посчитаем, сколько инверсий она прибавляет, сделав один запрос к этому Фенвику.

    Осталось научиться пересчитывать, что происходит, когда мы двигаем корень из одной вершины в другую. При переходе по ребру изменения затрагивают только два его конца — для вершины, которая была корнем, раньше инверсии считались со всем деревом, а теперь только с каким-то его поддеревом, так что надо посчитать, сколько есть вершин со значением больше текущего в каком-то поддереве графа; для вершины, которая стала корнем, все ровно наоборот. Эти запросы "сколько каких-то вершин в каком-то поддереве" можно перегнать в оффлайн и тоже порешать обычным Фенвиком/ДО по Эйлеровому обходу.

    Upd. Код.

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

      Спасибо за идею с Эйлеровым обходом! Вот чего мне не хватало)..

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

    Есть ещё такой вариант (но не писал): осуществить heavy-light декомпозицию и на каждом пути завести аддитивное дерево отрезков с обновлением на интервале, в котором мы будем считать количество инверсий для каждой вершины. Далее, добавляем числа на вершины в порядке убывания. Добавляя очередное число, мы видим, что в каждой ветке в каждой вершине добавилось столько инверсий, сколько уже было ранее добавлено чисел в других ветках. Обновляем информацию с помощью деревьев отрезков на путях.

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

    Я хранил для поддеревьев списки значений вершин в декартовом дереве по явному ключу и мерджил их от меньшего к большему, узнавая для каждой вершины v и каждого ребра из нее вниз в вершину to, сколько вершин в поддереве to больших, чем наша. Также в отдельном массиве складываем эти величины по всем to, а также считаем сумму этих величин по всем поддереве v. Потом считаем количество инверсий вершины v со всеми кроме поддерева v (запрос к итоговому декартову дереву + вычесть сумму в поддереве v), и уже по этой информации можно найти ответ еще одним dfs-ом по дереву (спускаем в вершину сумму количеств инверсий для вершин не в поддереве v, эта величина через полученную информацию легко пересчитывается). Код

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

Good interesting problems, thanks for the round!

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

Is there a simple approach for problem E? I have several overcomplicated in mind, however wasn't brave enough to implement any of them.

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

    First, you need to compute, for each vertex, how many vertices with a higher number are there in its subtree (nIni), in each of its childen's subtrees (nChildij), and outside of its subtree (nOuti).

    To compute the numbers, first sort the vertices in the order of preorder traversal. Each subtree will be represented by a contiguous interval in this order. Create a Fenwick tree. Process vertices in the order of assigned numbers, and use position in preorder traversal as an index in the Fenwick tree. Then, you will be able to compute how many already processed vertices are in any given subtree in .

    Then, you can move the root along edges and maintain current number of inversions in O(1) time for each move. Let's call "current root" the vertex you are currently computing the answer for, and "initial root" is the vertex that you used as a root in the previous steps. The answer can be computed as the sum of contributions of each vertex, where contribution of vertex i is defined as:

    • If vertex i is the current root, its contribution is nIni + nOuti.
    • Otherwise, if i is on the path between the current root and the initial root, its contribution is nOuti + nIni - nChildij, where j is the child that is in the direction of the current root.
    • Otherwise, its contribution is nIni.

    As you may see, when moving the current root along an edge, only contributions of two vertices may change (ends of that edge). So, the answer can be updated in O(1). Since it is possible to visit all vertices using O(n) such moves, this part takes O(n) time, and the entire solution takes time.

    Here is the code for better understanding
»
8 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

Last round I was afraid to submit anything blindly and because of that I was a victim of wrong tests in C. This round I thought I need to risk ans submit something blindly, so since I was pretty sure of D (it can't not work if it works on samples, right?), so I submitted it blindly and it turned out to be only one of my submissions that wasn't right :|. a=b case xd.

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

How to solve F?

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

Has somebody encountered WA #18 in problem D? I have no idea what's wrong with my code (linear recurrence -> fast matrix exponentiation).

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

    It is said in the editorial that A = B in that test.

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

      Well, there are smaller tests with the same condition (for example A = B = 100 in test #10), and I did

          std::vector<VI> A = vector<VI>(n, VI(n));
          A[0][b-1] += 1;
          A[0][a-1] += 1;
          REP(i, n-1) A[i+1][i] = 1;
      

      so it's something different I guess.

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

    Ooops, WA #18 checks int overflow (C >= 2^31). I feel stupid right now. =)

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

Are there any chances for me to get a T-shirt if I missed Yandex Algorithm Round 2?

»
8 лет назад, # |
Rev. 3   Проголосовать: нравится 0 Проголосовать: не нравится

How to solve E that fast? I create some BSTs and "merge" them in some way to count the inversion, which leads to an O(n log^2 n) solution. My code is a little bit complicated so I wonder if there's a better solution...

Edit: Well I just saw the editorial. It seems not easy to implement though...

Edit: I saw tonynater's code. Now I know how to solve it :P