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

Автор at1, 14 лет назад, По-русски
Да, мы действительно написали n*log(n). И действительно TL. Видимо бага.
Если кто-то найдет её в алгоритме или пошлет мне хороший тест буду очень признателен.

// Генерил рандомные деревья (через снм), ежей, цепочки и т.п., на ноуте за 1.5 сек работает.

Условие:

В дерево T ответить на запросы radius[v, i] - количество вершин достижимых из v не более, чем за radius[v, i].
|T| <= 105; |{radius[v, i]}| <= 3*105

Решение:

1. У нас есть дерево =)
2. Дерево T можно поделить на две части L и R по вершине t, так что 1/3|T| <= |L|,|R| <= 2/3|T|
   см. далее подробнее
3. Для каждой вершины, для каждого запроса считаем ответ для L и R (рекурсивные запуски)
4. Для каждого расстояния d считаем
   lensL[d] = <количество вершин в L достижимых из t не более, чем за d шагов>
   lensR[d] = <количество вершин в R достижимых из t не более, чем за d шагов>
5. Для каждого запроса radius[v,i] к вершине v из L добавляем к ответу lensR[radius[v,i] - depth[v]]
   (это в точности количество вершин достижимых в правой половине из v, т.к. у нас L и R "соприкасаются" только в t)
   Для каждого запроса radius[v,i] к вершине v из R добавляем к ответу lensL[radius[v,i] - depth[v]]
6. Склеиваем деревья

Комментарии к пункту 2.
1) по ребру делить гораздо проще, но не для бинарного дерева это неверно =(
2) Что такое поделить по вершине - раздвоить ее, и некоторые ребра присоединить к одному образу, а некоторые - ко второму.
3) Как найти где делить:
   3.1 Ориентировать dfs-ом из 0.
   3.2 Им же подсчитать размеры поддеревьев, висящих на исходящих ребрах.
   3.3 двигаемся в ветку, размер которой >= 2/3|T| пока можем (дерево уже ориентировано, это O(|T|))
   3.4 утверждается, что теперь мы стоим в вершине, из которой все ребра выходят в ветки <= 2/3|T|
4) Как делить:
   4.1 подсчитать размеры поддеревьев, висящих на исходящих ребрах.
   4.2 наибольшую ветку из t пихаем в R
   4.3 пихаем в L все подветки из t, пока не наберем хотя бы 1/3|T|
   4.4 остальные ветки пихаем в R
5) Почему поделилось (доказательство)?
   5.0 1/3|T| <= |L|, |R| <=> |L|, |R| <= 2/3|T| <=> 1/3|T| <= |L| <= 2/3|T| <=> 1/3|T| <= |R| <= 2/3|T|
   5.1 x <= 2/3|T| (x - размер максимальной ветки из t). Так что, 4.3 нам всегда хватит веток для выполнения 1/3|T| <= |L|.
   5.2 допустим 1/3|T| <= x <= 2/3|T|. Тогда оставшиеся ветки в сумме не превосходят 2/3|T|, даже если все запихнуться в L, будет выполнено 1/3|T| <= R.
   5.3 если x <= 1/3|T|, то при добавлении в L очередной ветки |L| <= 2/3|T| (т.к. на предыдущем шаге |L| <= 1/3|T| и прибавили не более x). Значит 4.3 остановится при |L| <= 2/3|T|.

UPD: Замечания

Сережа (Burunduk1) предложил гораздо более простой способ разбиения по вершине:
1. Dfs ориентирует ребра и считает размеры поддеревьев.
2. Получая размер очередной подветки он прибавляет его к size[v], и, если оно превосходит 1/3|T|, то текущая вершина - это вершина, по которой необходимо разделить дерево, при чем подсчитанные ветки кладем в L, а не подсчитанные в R (ребро к предку тоже в R).

Почему это работает:
1. на каждом шаге размер всех учтенных подветок < 1/3|T| (база - это листья размера 1). Значит когда мы прибавляем к счетчику size[v] < 1/3|T| размер поддерева size[u] < 1/3|T|, результат будет не более 2/3|T|. Значит в вершине, в которой счетчик превзойдет 1/3|T| будет выполнено:
1/3|T| <= size[v] <= 2/3|T|, тогда построенные L, R тоже удовлетворяют этому соотношению.

Это один короткий dfs вместо двух длинных, особая бурундучья магия.
  • Проголосовать: нравится
  • +33
  • Проголосовать: не нравится

14 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
Есть одно предположение по поводу TL, (у нас такое же решение прошло), скажите как именно вы делите дерево на 2, в реализации (это очень тонкий момент)? вы явно добавляете новое ребро или как?
Если нет, то как вы в рекурсивных подзапросах обходили нужные вершины не ходя по ненужным? 
P.S. Да кстати мы у себя в решении делили по центру дерева.
  • 14 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится
    1. Центра вроде как не существует, я почти совсем уверен в этом.
    2. Было два решения, одно красило деревья, второе явно добавляло 2 вершины вместо одной + их ребра.
    • 14 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится
      1. Что значит центра не существует? Центр дерева - это вершина максимальное минимальное расстояние от которой до листа максимально. 
      2. Хорошо спрошу так, вы проходили в списке смежности при обходе в подзапросе какие нибудь вершины которые не относятся к подзапросу?
      • 14 лет назад, # ^ |
        Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится
        1. В таком определении, конечно, я с Вами согласен, я подумал, что Вы его поровну поделили просто, а это невозможно.
        2. Конечно, дерево делили на два несвязанных дерева, чтобы не смотреть ничего лишнего.
14 лет назад, # |
Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится
Можно уточнить как посчитать пункт 4 чтобы получился nlogn? У меня пока только nlog^2n выходит
UPD: все, понял
14 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
А почему все хотят разделить дерево именно на две части? Я в своём решении находил вершину, все поддеревья которой по размеру <= N / 2, и делил дерево на эти поддеревья. В остальном алгоритм тот же. 
  • 14 лет назад, # ^ |
    Rev. 3   Проголосовать: нравится 0 Проголосовать: не нравится
    Интересный вариант, я о нем даже не подумал. Только объединять потом чуть-чуть сложнее, зато можно вершину из дерева удалить навсегда, по которой разбиваемся