Всем привет! Не буду долгих предисловий тут писать, поэтому сразу к делу. Читая книгу Мейерса "Эффективное использование STL", я вдруг наткнулся на упоминание о наличии в некоторых версиях STL структуры данных rope. Если кратко, то эта структура данных позволяет быстро вырезать/вставлять куски массива в произвольные позиции, аналогично декартовому дереву по неявному ключу (с аналогичной сложностью — подробности смотрите в статье на вики). Она иногда используется для обработки сверхдлинных строк.
Как выяснилось, rope действительно реализована в некоторых версиях STL, например в SGI STL. Сразу замечу, что это наиболее полная документация по классу rope, которую мне удалось найти в сети. А теперь давайте разыщем rope в GNU C++. Поскольку кто-то ранее находил расширенную версию красно-черных деревьев в GNU, я подумал, почему бы и rope где-нибудь не заваляться. Для тестинга я взял вот эту задачу, в которой у меня уже была сдана дерамида по неявному ключу. После недолгого гугления получилось следующее решение:
#include <iostream>
#include <cstdio>
#include <ext/rope> //заголовочный файл с rope
using namespace std;
using namespace __gnu_cxx; //пространство имен, в котором находится класс rope и все, что с ним связано
int main()
{
ios_base::sync_with_stdio(false);
rope <int> v; //используем как самый обычный stl контейнер
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; ++i)
v.push_back(i);
int l, r;
for(int i = 0; i < m; ++i)
{
cin >> l >> r;
--l, --r;
rope <int> cur = v.substr(l, r - l + 1);
v.erase(l, r - l + 1);
v.insert(v.mutable_begin(), cur);
}
for(rope <int>::iterator it = v.mutable_begin(); it != v.mutable_end(); ++it)
cout << *it << " ";
return 0;
}
Оно без всяких проблем зашло и уложилось в секунду, что в 2 раза медленнее варианта с рукописным декартовым деревом, но при этом с чуть более оптимальным расходом памяти. Полную документацию по всем методам можно найти по вышеприведенной ссылке на SGI STL. Судя по всему, в GNU полностью аналогичная реализация. Visual C++ как унылое го... не поддерживает rope.
Что касается применения, то есть несколько особенностей. Из документации SGI STL следует, что класс rope плохо дружит с изменениями отдельных элементов последовательности (поэтому методы begin() и end() возвращают const_iterator. Чтобы получить обычный итератор, необходимо вызывать mutable_begin() и mutable_end() соответственно.). Также нельзя просто взять и присвоить значение i-ому элементу последовательности (см. код ниже для подробностей). Но насколько это "плохо", я не знаю. По идее за классический логарифм от числа элементов. В то же время конкатенация (операция +=) вообще работает за O(1) (не считая затрат на создание объекта справа, конечно).
Особенности индексирования можно увидеть в этом коде: http://pastebin.com/U8rG1tfu. Поскольку разработчики очень не хотят, чтобы мы меняли отдельные элементы контейнера, оператор [ ] возвращает ссылку на константу, но специальный метод все же есть. Данный код работает столько же, сколько и предыдущий. И да, забыл упомянуть, что все итераторы RandomAccess, что не может не радовать.
Если кто захочет потестировать контейнер на более серьезных задачах, расскажите в комментариях. По моему ощущению, у нас теперь есть быстрый массив с логарифмическим временем выполнения всех операций и как обычно большой константой :)
UPD: сдал еще задачу. Здесь мало запросов, но последовательность подлиннее. Зашло тоже без проблем. Еще наткнулся на такую особенность, что erase может принимать только 1 индекс типа size_t (хотя метода с такой сигнатурой нет). И я не очень понимаю, что он делает в этом случае. Из-за этого можно случайно набажить, когда нужно удалять 1 элемент.
действительно сверхдлинные строки можно хранить. этот код в запуске отрабатывает за 300 мс
upd. этот за 140мс
upd2. 292E - Копирование данных сдать не удалось
Попробовал вариант с
replace
, тоже не прокатило. Так что не все так хорошо) А там обычный treap заходит?treap тоже не будет заходить, так как в задаче не вырезал/вставил, а скопировал/заменил.
Интересно, как оно ведет себя в тех задачах, где проходит treap. Я просто давно уже не участвую и кроме приведенной в посте задачи не помню задачи на неявный treap.
Будет. Вот здесь все очень подробно объяснили, от себя мне добавить нечего.
мм, ну ок. просто для меня персистентное декартово дерево — уже не декартово дерево.
.
не зашло
А вот здесь декартово у меня залетело. По-видимому при 105 запросах и ограничении в секунду rope лучше не использовать.
.
Вот здесь наиболее полное описание реализации, что мне удалось найти. Вот какое именно бинарное дерево используется, тут не сказано. Однако, в коде (в ropeimpl.h) я нашел список чисел Фибоначчи рядом с
_S_max_rope_depth
и_S_min_len
функцией_S_balance
. Я не в курсе, как реализуется rope, но может это АВЛ?АВЛ как-то жестко по неявному ключу. Вот сплей дерево умеет и за О(1) конкатенироваться и все остальное перечисленное.
P.S. не знаю на чем rope, просто флужу
Никто не будет писать splay, потому что у него хорошая только амортизированная оценка. Да и возможно есть некоторые нюансы по сравнению с обычными деревьями поиска, препятствующие использованию splay.
Хорошая амортизированная оценка — это, вообще-то, хорошо. У добавления элемента в вектор тоже только амортизированная оценка константная.
Да, это я ляпнул, не подумав. Но все равно никогда не видел, чтобы Splay-дерево использовалось в какой-либо стандартной библиотеке. Оно хоть и изящней всех остальных, но проигрывает тому же красно-черному, например.
ведь? То есть я однажды слышал, что якобы merge-и делаются за амортизированные O(1), но не очень понимаю, с чего бы быть такому. Можно поподробнее?
На самом деле я не знаю такую амортизированную оценку. Я просто подумал, что нам ничего не мешает создать вершину-корень, в которой будет пустая строка и прицепить слева и справа то, что конкатенируем. Свойства вроде как не нарушаются.
P.S. Нашел такую ссылку: http://habrahabr.ru/post/144736/
Да. Но во первых плодятся лишние вершины на пустые строки, во вторых растет потенциал, который есть в оценке. Поэтому все-таки лог.
Это еще почему? Оценка конкатенации — явно константа.
То, что ты предложил, просто ломает все остальные асимптотики. Ты же не мёрджишь так декартовы деревья? Попробуй приконкатенировать 105 одноэлементных кусочков по очереди следующий к предыдущему. Если следовать твоему алгоритму, получится бамбук глубины 105.
Амортизированное время = реальное + изменение потенциала в правильную сторону. На этом строится оценка Splay в общем-то. Потенциал, который есть в доказательстве, которое я видел увеличивается на O(logn) при merge. Это дает логорифмическое время в этом смысле.
There is also an order-statistics tree in the SGI library included in STL.
Sample code: http://ideone.com/QuiYER
.
Cool! I have also found the patricia trie and splay tree there as well as the binomial heap and some other data structures, but it seems to me that they are incomplete.
Well, Last night I was investigating contents of pb_ds (Politics based data strucutres) library, included by default in g++. I've found that implementations of binary search trees there are almost as we need: they support traveling with node iterators (such as moving to the left/right child, dereferencing and everything we need), splitting, mergeing and even contatining additional information in each node with its recalculation after every change of tree structure (such as size of subtree, minimum or maximum)!
Here are two examples of what we can do with this library.
First one. Basic examples of usage. Look over it, it's very interesting! It also compiles here on Codeforces.
Second one with solution of RMQ problem using this tree. When I was writing it I was thinking like "That's really great! That can replace treap as default binary search tree that people often write on contests! That's very cool!". But...
The main problem is that splitting is implemented in linear time because of very stupid bottleneck: after split they set the size of second subtree as
std::distance(other.begin(), other.end()
, that works in linear time. =(There is also implementation of 4 types of heap. They support iterators to fixed elements of the heap, that makes us possible to write, for example, Dijkstra with heap algorithm. But here is a benchmark, that shows that it is still 20-30% slower then usual Dijkstra+set implementation.
Patricia tree was a surprise for me, essentially after I looked in wikipedia and found a compressed trie (!) picture. But in fact it is another associative container for storing keys in increasing order:
(from official documentation of that library)
There is also forward-linked list and bunch of hashmaps (I didn't tested yet).
That trees are really what we could use in our contests, If it hadn't linear-time split. I've written an e-mail to the developers of that library with some questions about why they didn't implement it more efficient. Waiting for the answer from them.
What's their point of doing this in Linear time when it can be done in constant time? This is really weird! Nice effort by the way I really appreciate it :)
I also found a skip-list implementation, it allows finding, searching, deleting all in logarithmic time.
Excuse me, but in which file did you find the linear split method ? As from what I can see in this page
http://gcc.gnu.org/onlinedocs/libstdc++/ext/pb_ds/tree_based_containers.html
Scroll to the end of the page right to (Additional Methods)
You will find this: "These methods are efficient — red-black trees are split and joined in poly-logarithmic complexity; ordered-vector trees are split and joined at linear complexity. The alternatives have super-linear complexity."
So only ordered-vector trees are splitted in linear time, you should be alright if you use red-black tree as the underlying data structure by specifying the rb_tree_tag when creating the object?
I'll look into the implementation of the split method inside..
No, split will be anyway linear-time. After tree-depend implementation of split function there is a call of general function for all binary trees
split_finish(other)
, where follows next line:(/ext/pb_ds/detail/bin_search_tree_/split_join_fn_imps.hpp, #134).
Of course
std::distance
for iterators of this tree works in linear time (it shifts first iterator until it is equal to the second iterator). You can run my solution for RMQ on big test to ensure that I'm right. It's surprising, but in this place official documentation is wrong.Aha I see...
Did the authors reply by now? I think it was a design issue because to update the tree size quickly one needs the subtree size augmentation that is only provided by the
tree_order_statistics_node_update
mixin.We can work around the problem by overloading
std::distance
, but it's not pretty: https://github.com/niklasb/contest-algos/blob/master/stl_splay_tree.cpp Overall it takes a lot of code to reimplement order statistics, so it's probably not really worth the effort, since a manually coded treap seems to be much faster and doesn't require a lot more code.Is it possible to construct order-statistics tree quickly or use something like emplace_hint?
можна ли решать с помощью сия чуда эту и эту задачи? Если так подумать, то в роупе можно хранить какие-то структуры даных, например дерево отрезков, или же при изменениях порядка элементов будет полностью нарушаться корректность надстройки над частями контейнера?
да, наверно фигню сказал...
Есть ли структура типа rope которая способна разворачивать подотрезок масива за O(logN)?
Переворот на отрезке можно реализовать с помощью двух rope. В одной храним прямой массив, в другой — развернутый. Перевернуть — то же самое, что поменять соответствующие отрезки местами.
Спасибо.
Правда ли, что эта штука персистентная?
What if we want to detach a block and set it anywhere else after reversing the block? Can we do this efficiently?
@ Perlik,Can I get the pdf link of "Effective STL"-by Meyers.
http://www.uml.org.cn/c%2B%2B/pdf/EffectiveSTL.pdf
дополню информацию про erase, где аргументом выступает один индекс типа size_t. Решение было найдено после изучения этой функции внутри дерева. А именно:
думаю здесь все понятно: он убирает с позиции __p __p+1 элемент
very helpful
А есть что-то наподобие rope, которое может еще сортировать автоматически элементы?
wow , its great