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

Автор adamant, 13 лет назад, По-русски

Всем привет! После достаточно долгого затишья, я решил, что мой вклад растёт слишком медленно пришёл час порадовать вас ещё одной статьёй в блоге :)

2 месяца назад пользователь Perlik выложил статью, в которой описал очень интересную реализованную в STL структуру, которая позволяла очень быстро производить различные операции с подстроками. Некоторое время после этого я тестировал её на различных задачах и, к сожалению, как правило, получал негативный результат — rope оказалась слишком уж медленной, особенно когда речь шла о работе с отдельно взятыми элементами.

На некоторое время я позабыл о той статье. Однако всё чаще я сталкивался с задачами, в которых нужно было реализовать множество, да не простое, а золотое с возможностью узнавать порядковый номер элемента и, напротив, элемент по его порядковому номеру (т.е., порядковую статистику в множестве). Вопрос выполнения этих операций методами стандартной библиотеки достаточно тонкий и актуальный, подтверждением тому можно привести, например, этот и этот блоги. И тут я вспомнил о том, что в комментариях к той статье кто-то упомянул о загадочной структуре данных order statistics tree, которое как раз поддерживает эти две операции и которое как раз реализовано в STL (хотя и только для GNU C++). Тут и началось моё увлекательное знакомство со структурами данных, основанными на политике (policy based data structures), о которых я хочу поведать и вам :)

Итак, приступим. В данной статье я рассмотрю наиболее интересную из реализованных структур — дерево (tree). Нам потребуется подключить следующие библиотеки:

#include <ext/pb_ds/assoc_container.hpp> // Общий файл. 
#include <ext/pb_ds/tree_policy.hpp> // Содержит класс tree_order_statistics_node_update

При более внимательном рассмотрении можно обнаружить, что последние два файла содержатся в библиотеке

#include <ext/pb_ds/detail/standard_policies.hpp>

Пространство имён, с которым нам прийдётся работать в современных версиях носит название __gnu_pbds; Ранее оно называлось pb_ds;

Теперь рассмотрим конкретные структуры.

Шаблон tree имеет следующий вид:

	  template<
	  typename Key, // Тип ключа
	  typename Mapped, // Тип ассоциированных с ключём данных
	  typename Cmp_Fn = std::less<Key>, // Функтор сравнения, должен соответствовать оператору <
	  typename Tag = rb_tree_tag, // Метка, обозначающая тип дерева
	  template<
	  typename Const_Node_Iterator,
	  typename Node_Iterator,
	  typename Cmp_Fn_,
	  typename Allocator_>
	  class Node_Update = null_node_update, // Метка обновления вершин
	  typename Allocator = std::allocator<char> > // Аллокатор
	  class tree;
	

Опытные участники, должно быть, уже заметили, что если инициализировать в шаблоне только первые 2 типа, то мы получим практически точную копию контейнера map. Сразу оговорюсь, что из этого контейнера можно сделать set, для этого нужно просто указать вторым аргументом шаблона тип null_type (в старых версиях null_mapped_type).

А вот параметры Tag и Node_Update в обычном map'e отсутствуют. Рассмотрим их подробнее.

Tag — класс, обозначающий структуру дерева, которое мы будем использовать. В STL реализовано три класса, которые мы можем здесь использовать, это rb_tree_tag, splay_tree_tag и ov_tree_tag. На практике, однако, допустимо использование только красно-чёрного дерева, spay_tree и ov_tree же, по всей видимости, используют при обновлении линейную по времени операцию split, что и лишает их возможности к применению при решении олимпиадных задач.

Node_Update — класс, обозначающий политику обновления инвариантов вершин. По умолчанию он установлен в null_node_update, т.е., дополнительной информации в вершинах не храниться. Кроме этого, в C++ реализована политика обновления tree_order_statistics_node_update, которая, собственно, и несёт в себе необходимые операции. Рассмотрим их. Скорее всего, наилучшим способом задания дерева будет следующий:

typedef tree<
int,
null_type,
less<int>,
rb_tree_tag,
tree_order_statistics_node_update>
ordered_set;

Если мы хотим получить ассоциативный массив, а не множество, в качестве второго аргумента следует использовать тип, соответствующий значению в этом множестве. По всей видимости, дерево поддерживает все те же операции, что и set (по крайней мере, до этого у меня с ними конфликта не возникало), но кроме этого добавлены две новые функции — это find_by_order() и order_of_key(). Первая возвращает итератор на k-ый по величине элемент (отсчёт с нуля), вторая — возвращает количество элементов в множестве, строго меньших, чем наш элемент. Пример использования:


ordered_set X; X.insert(1); X.insert(2); X.insert(4); X.insert(8); X.insert(16); cout<<*X.find_by_order(1)<<endl; // 2 cout<<*X.find_by_order(2)<<endl; // 4 cout<<*X.find_by_order(4)<<endl; // 16 cout<<(end(X)==X.find_by_order(6))<<endl; // true cout<<X.order_of_key(-5)<<endl; // 0 cout<<X.order_of_key(1)<<endl; // 0 cout<<X.order_of_key(3)<<endl; // 2 cout<<X.order_of_key(4)<<endl; // 2 cout<<X.order_of_key(400)<<endl; // 5

Как мне кажется, наличие такой структуры в STL просто замечательно, т.к. оно позволяет ещё сильнее сузить круг задач, в которых реализация какой-нибудь общеизвестной структуры требует порой больше времени, чем придумывание решения. Напоследок хотелось бы сказать о производительности order_statistics_tree в STL. Для этого я приведу следующую таблицу задач, на которых я её тестировал и результаты по ним.

Решение\Задача 1028 1090 1521 1439
order_statistics_tree, STL 0.062 0.218 0.296 0.468
Дерево Отрезков 0.031 0.078 0.171 0.078
0.859*
Дерево Фенвика 0.031 0.062 0.062
-

* В последней задаче для реализации решения за O(mlogn) необходим прямой доступ к вершинам дерева. Без него решение работает за O(mlogn*logn).

Как можно из всего этого видеть, order_statistics_tree сравнительно немного отстаёт от рукописных структур, а порой и вовсе их обгоняет. При этом размер кода сокращается значительно. Отсюда можно сделать вывод, что order_statistics_tree — это хорошо и его можно использовать в контестах.

Кроме tree, я также хотел описать здесь trie — реализованный префиксный бор. Однако, меня смутили некоторые аспекты его реализации, сильно ограничивающие его применимость в олимпиадном программировании, в связи с чем его описание я здесь приводить не буду. Если кому-то интересно, он может самостоятельно попытаться узнать об этой структуре больше.

Полезные ссылки:
— Документация по pb_ds
— Тестирование производительности pb_ds
— Помощь с использованием pb_ds
— Демонстрация использования order_statistics_tree
— Демонстрация использования бора с поиском строк, включающих данную строку в качестве префикса
— Операции с интервалами с помощью самописного класса обновления вершин
— Прочие примеры

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

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

Example of trie with search of prefix range.
Problem: 1414
Solution: http://ideone.com/6VFNZl

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

Возможно, вам покажутся слегка нетривиальными решения деревом отрезков и деревом Фенвика, особенно, задач 1521 и 1439. Скорее всего, позже я также предоставлю статью, в которой опишу некоторые интересные способы использования этих структур, которые редко встречаются.

======================================================================================= You may be wondered about how I use segment tree and binary indexed tree in my solutions, especially for problems 1521 and 1439. Most likely, later I'll provide an entry about some interesting ways of using this structures, which are quite rare.

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

This is really useful. Thanks a lot!

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

Very useful article! I need order-statistics on a multiset. How should I define the tree as?

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

.(same comment as above)

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

А начиная с каких версий плюсов tree<...> компилируется?

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

    Затрудняюсь ответить на этот вопрос :(

    Пока только могу сказать, что на некоторых совсем старых версиях необходимо писать null_mapped_type вместо null_type. Например, на 4.3.2, судя по компилятору с http://ideone.com.

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

what will be the complexity of erase operation? O(logn) or O(n)

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

Is there any efficient function to merge 2 trees ?

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

how can i use it like multiset ?

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

Hi, adamant, the code files in Useful Links don't seem to work. Could you fix them?

Thanks for this great post. I am looking forward to your next and next next posts.

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

What is the complexity of these function : find_by_order() and order_of_key() ?

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

https://www.e-olymp.com/ru/problems/2961 Is it possible to solve this problem using this algorithm?

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

[del]

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

Can anybody share a Java equivalent class for this type of set or a code which acts according to above data structure?

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

    It does not exist.

    You may use instead:

    • self-written tree
    • treap
    • numbers compression + fenwick tree
    • »
      »
      »
      9 лет назад, скрыть # ^ |
       
      Проголосовать: нравится 0 Проголосовать: не нравится

      I thought of number compression + fenwick tree, but this solution will work for only offline queries. I want to handle online queries. The best I can think of now is Treap + Segment Tree or Treap + Fenwick Tree. But here again is the problem of implementation of mixed data structure, I am unable to think how to implement that. Can you please help me?

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

у этой штуки правда нету count?

(понятно, что можно *s.find_by_order(s.order_of_key(x)) == x, но не красиво же)

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

Any idea on how to use this pre C++11?

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

How can I use a custom compare function in the "Key comparison functor" section for custom data types?

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

Is it possible to search for the order of a non-key value by passing a comparator/function ? I sometimes find myself have to use my own tree templates instead because of having to write a search function that can cope with this task.

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

What is the constant factor of order_statistics_tree though it executes in logarithmic complexity ? I think it's constant factor is very high.

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

could anyone write the exact thing to use this data structure as map..pls.I'm not able do so.

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

can we use it as multiset?

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

is it legal to use it in ICPC or any region contest ??

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

How can I erase element from order_set by it's value?

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

(Sorry for necroposting) Does anyone know how to compile with the pbds header files on Mac OS X ? I think the g++ command is configured to use clang by default, and so it is not directly available. I've tried adding ext/pb_ds into the include folder (the same way you would enable bits/stdc++.h) but instead new dependencies come up.

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

    For me, I installed the latest version of gcc (gcc 9.3.0_1) and compiled with gcc-9. It works on Mojave and Catalina and it should work on High Sierra (but I haven't tested it).

    To install gcc-9, I used brew and the command brew install gcc@9

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

      (Again, sorry for necroposting) There is an important step that I've so-far seen all the mac g++ setup instructions ignore/skip. I also installed gcc through brew, but neither bits/stdc++.h header nor pbds data structures seemed to work. Putting the bits/stdc++.h file manually in the /usr/local/include folder allowed me to at least solve the header problem, but trying the same method for pbds spawned a lot of dependency issues.

      The problem was that brew sets up the g++ command as g++-10 so that it doesn't clash with the default g++ command mac provides. So, alias-ing g++ to g++-10 in the .bashrc/.zshrc file would be enough for solving the issue if you compile using the terminal. But if you compile using an editor and the editor directly uses the /usr/bin/g++ binary for compilation, then alias-ing g++ wouldn't work anymore. For example, most of the popular VSCode extensions for CP I've seen use /usr/bin/g++ to compile. I wasn't aware that this was the root of the issue and had been missing pbds structures for a long time. The way to solve it is to simlink g++ to g++-10 and prepend it to the PATH variable so that the simlink is prioritized before the default g++.

      Instructions for the simlink way

      I might even find it useful myself if I ever need to setup a mac again in future.

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

        (Sorry for Necroposting). But I have to update the details as from M1 onwards (possibly Big Sur) configurations have been changed. The Cellar directory is not available at /usr/local anymore. You can do the following instead.

        cd /opt/homebrew/Cellar/gcc/12.2.0/bin

        ln -s ./c++-12 ./c++

        ln -s ./g++-12 ./g++

        export PATH=“/opt/homebrew/Cellar/gcc/12.2.0/bin:$PATH”

        Did you manage to solve PBDS issue on your mac ? Doing all above doesn't solve the PBDS issue.

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

          Thanks for the updates.

          Yes, PBDS seems to work for me. I'm running macOS Ventura on M1 (Btw I had to reinstall the command-line tools when updating from Big Sur to Ventura). Can you elaborate on the exact issue you're facing, or possibly provide some error logs?

          Also, did you try compiling a source code that uses PBDS from the terminal? Because I've fixed similar issues for others who were compiling/running from an editor/IDE, but the main culprit was actually the editor. The editor might fail if you didn't properly point it toward the Homebrew g++.

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

            I am also using ventura 13.2 on M1. I tried both on sublime build and manually on terminal to run a sample code. It shows the following error.

            In file included from B.cc:13:
            /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/ext/pb_ds/assoc_container.hpp:44:10: fatal error: 'bits/c++config.h' file not found
            #include <bits/c++config.h>
            

            The .zshrc profile looks like as mentioned in the above command. I tried to add the missing file, then got reported for another missing file and so on. In fact the ext/pb_ds directory I put there myself.

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

              I think it might have to do with the configs in .zshrc. I checked my .zshrc file and there seem to be some additional env variables I defined but I forgot why. I have these at the very top of the .zshrc file:

              eval "$(/opt/homebrew/bin/brew shellenv)"
              
              # g++
              CPLUS_INCLUDE_PATH=/opt/homebrew/Cellar/gcc@12/12.1.0_1/include/c++/12:/opt/homebrew/Cellar/gcc@12/12.1.0_1/include/c++/12/aarch64-apple-darwin21:/Users/drswad/Desktop/CP/Setup/include
              export CPLUS_INCLUDE_PATH
              export PATH=/opt/homebrew/Cellar/gcc@12/12.1.0_1/bin:$PATH
              
              # other configs ...
              

              Check if you have similar paths and modify them accordingly before putting them in .zshrc. Also, the last path there (/Users/drswad/Desktop/CP/Setup/include) is just for any custom header files. I put my debugging template and testlib.h in there, so that they always get included in every program on my PC and the editor linter doesn't complain with red squiggly lines.

              Also, try reinstalling command-line tools with xcode-select --install.

              Let me know if they resolve your issue.

»
7 лет назад, скрыть # |
← Rev. 2  
Проголосовать: нравится +6 Проголосовать: не нравится

Note on using less_equal as comparison function to use it as a multiset:

  • _GLIBCXX_DEBUG must not be defined, otherwise some internal check will fail.
  • find will always return end.
  • lower_bound works like upper_bound in normal set (to return the first element > it)
  • upper_bound works like lower_bound in normal set (to return the first element >= it)
  • find_by_order and order_of_key works properly (unlike the 2 functions above).

Some code to verify the points above: Try it online!

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

adamant, while discussion, someone suggested that the time complexity of using it as a set is amortized log(n), and this post says that it means that in some cases that can be O(n). I wonder if that is true ?? If yes, is there an alternative to policy based data structures ?? Here is one solution

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

Can i merge two ordered_set ? if it can be what will be its complexity?

»
7 лет назад, скрыть # |
← Rev. 3  
Проголосовать: нравится -10 Проголосовать: не нравится

Как реализовать ordered set для произвольных самописных структур, если для структуры прописан оператор < ?

UPD: Уже ответил на свой вопрос.

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

Thanks,it just got me an AC.

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

If someone is having trouble to use these in windows with mingw compiler, try to find hash_standard_resize_policy_imp.hpp0000644 in MinGW\lib\gcc\mingw32\6.3.0\include\c++\ext\pb_ds\detail\resize_policy and rename it to hash_standard_resize_policy_imp.hpp. I dont know why it is named like this.

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

I have defined a bool cmp(pair<int,int> a, pair<int, int> b) for comparing pairs. Is it possible to use that as the comparator for the ordered_set?

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

Thank you this really helped me.

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

Is this actually STL? I only see files with gcc's implementation of the c++ standard library. Actual STL is quite old (the linked post references SGI's docs, and SGI doesn't even exist any more)

»
7 лет назад, скрыть # |
← Rev. 2  
Проголосовать: нравится +1 Проголосовать: не нравится

This is a GNU extension, so it has nothing to do with the STL which (incorrectly) refers to the C++ standard library.

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

The first returns an iterator to the k-th largest element (counting from zero)

Shouldn't it be the k-th smallest element? adamant

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

How to merge two ordered sets?

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

A very Useful article helped me in a lot of places. Thank you for your contribution.

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

When to use BIT over PBDS .. if they perform same thing instead of memory constrain

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

When to use BIT over PBDS other then memory limit

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

can we use pair in place of int ??

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

Is insert not too slow? I tried 10^7 insertions and it took over a minute.

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

Doesn't find_by_order(k) returns the kth smallest element in the set? In the article the values given seems like the kth smallest ones not the largest ones

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

Shouldn't this line

"The first returns an iterator to the k-th largest element (counting from zero),"

be

"The first returns an iterator to the k-th **smallest** element (counting from zero),"

because X.find_by_order(1) clearly returns 2nd smallest number.

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

Just saw this post and I am wondering can https://codeforces.me/contest/237/submission/2804338 be the first submission using pbds? (I did not actually used pbds in this submission just included it as part of my template.) IIRC, Gerald used to see my usage of this trick in Dec, 2013 and asked about its usage.

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

Can I make the ordered set get the first element greater than x or even greater than or equal?

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

Please note erase(key) does not work if you are using ordered_map typedef tree< int, map_value_type, less<int>, rb_tree_tag, tree_order_statistics_node_update> ordered_map;

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

Thanks a lot bro

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

can we use this in IOI

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

Could not implement it in time for today's E :(

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

12 Years Later...

1) While going through the official documentation of Policy Based Data Structures , I found an internal test claiming that `ov_tree_tag' is better for split and join methods.

The test can be found here.

2) Moreover, we can't use tree_order_statistics_node_update with the ov_tree_tag.

I can't find any proof for this :(

3) After some years, the links to some of the codes seem to be down. They can be found in this git repository.

Thank you for this amazing blog !

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

For Anyone who wants to try this concept

1915F - Greetings This question is a good and simple implementation of ordered set.

»
10 месяцев назад, скрыть # |
 
Проголосовать: нравится -11 Проголосовать: не нравится
#include<bits/extc++.h>
using namespace __gnu_pbds;
typedef tree<
int,
less<int>,
null_type,
rb_tree_tag,
tree_order_statistics_node_update
>ordered_set;