EduBrick

Тяжёлые и лёгкие вершины

Сумма степеней равна 2m, поэтому вершин со степенью больше корня — меньше корня. Отсюда треугольники за m корень из m.

3 мин

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

Наблюдение

Сумма степеней всех вершин равна 2m2m. Значит, вершин со степенью больше 2m\sqrt{2m} не может быть больше 2m\sqrt{2m} - иначе сумма степеней превысила бы 2m2m.

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

  • лёгкую вершину можно перебрать целиком - это O(m)O(\sqrt m);
  • тяжёлых мало, поэтому про них можно позволить себе предподсчёт.

Дальше остаётся разобрать случаи и убедиться, что ни в одном не осталось дорогой ветки.

Ориентация рёбер по степени

Есть вариант того же приёма, в котором делить вершины на два сорта не приходится. Направим каждое ребро от вершины меньшей степени к вершине большей, а при равных степенях - от меньшего номера к большему.

Тогда исходящая степень любой вершины не больше 2m\sqrt{2m}. Доказательство в одну строчку: если у vv исходящая степень kk, то все kk её целей имеют степень не меньше degvk\deg v \ge k, значит сумма степеней не меньше k2k^2, а она равна 2m2m.

Сравнение по паре «степень, потом номер» задаёт строгий порядок, поэтому циклов в ориентированном графе не возникает.

Треугольники за O(mm)O(m\sqrt m)

После ориентации каждый треугольник становится путём uvwu \to v \to w с ребром uwu \to w и находится ровно один раз: у трёх вершин треугольника есть единственная наименьшая по этому порядку.

for (int u = 0; u < n; u++) {
    for (int v : outgoing[u]) mark[v] = 1;
    for (int v : outgoing[u])
        for (int w : outgoing[v]) if (mark[w]) triangles++;
    for (int v : outgoing[u]) mark[v] = 0;   // тем же списком, не memset
}

Внутренний перебор стоит

uvout(u)out(v)  2muout(u) = O(mm).\sum_u \sum_{v \in out(u)} |out(v)| \ \le \ \sqrt{2m} \sum_u |out(u)| \ = \ O(m\sqrt m).

Очистка пометок тем же списком, которым их ставили, - не мелочь. memset на весь массив внутри цикла по вершинам превращает O(mm)O(m\sqrt m) в O(n2)O(n^2), и это самая частая ошибка в этой задаче.

Чтобы разложить треугольники по вершинам, достаточно вместо одного счётчика увеличить три - именно потому, что каждый треугольник найден один раз, а не трижды.

Прибавление соседям

Классическая задача: «прибавить xx всем соседям вершины vv» и «узнать число в вершине vv». В лоб прибавление стоит degv\deg v, и вершина с миллионом соседей всё ломает.

операция лёгкая вершина тяжёлая вершина
прибавить соседям честно, O(m)O(\sqrt m) запомнить: pending[v] += x
узнать значение vv own[v]own[v] плюс сумма pending по тяжёлым соседям то же самое

У любой вершины тяжёлых соседей не больше 2m\sqrt{2m}, поэтому обе операции стоят O(m)O(\sqrt m).

Общие соседи

Запрос «сколько общих друзей у uu и vv» разбирается по трём случаям:

  • обе лёгкие - слияние двух отсортированных списков, O(m)O(\sqrt m);
  • одна тяжёлая - проход по списку лёгкой с проверкой пометки, O(m)O(\sqrt m);
  • обе тяжёлые - ответ берётся из таблицы, посчитанной заранее.

Таблица считается одним проходом: для каждой вершины ww перебираются пары тяжёлых среди её друзей, и каждая такая пара получает от ww единицу. Стоит это w{тяжёлые друзья w}22m2m\sum_w |\{\text{тяжёлые друзья } w\}|^2 \le 2m\sqrt{2m}.

Порог не обязан быть ровно 2m\sqrt{2m}: он выбирается так же, как размер блока, - из равенства двух стоимостей. Если предподсчёт про тяжёлые дороже перебора лёгких, порог сдвигают.