Pull to refresh

Декартовы деревья по неявному ключу + сжатие пространства

Reading time3 min
Views3.5K
Прежде чем читать эту статью, нужно понимать, что такое декартово дерево по неявному ключу (это тема не одной статьи, поэтому об этом лучше почитать тут). Сжатие пространста — метод, используемый для сжатия на отрезке данных. Например, вместо того, чтобы хранить множество {1, 1, 1, 2, 2, 2, 3, 1, 1} будем хранить {1 x 3, 2 x 3, 3 x 1, 1 x 2}.
Теперь попробуем сжимать пространство с помощью такого метода и иметь возможность выполнять онлайн операции с любым отрезком.

Сразу приведу свою реализацию таких деревьев
struct treap_node
{
   int x, y;
   treap_node *l, *r, *p;
   int width;
   int val;

   // Проталкивание ленивых операций
   void push()
   {
      // Место для вставки кода отложенных операций
   }

   // Сыновья изменились, нужно обновить текущую вершину
   void update()
   {
      x = width;
      if (l)
            x += l -> x;
      if (r)
            x += r -> x;
      if (l)
            l -> p = this;
      if (r)
            r -> p = this;
      // Вставить код обновления, в зависимости от того, что делает наша структура данных

   }

    treap_node(int _width, int _val)
    {
          // начальная инициализация
          // val - хранимое значение, width - ширина хранимого отрезка
         y = (rand() << 16) + rand();
         l = r = p = NULL;

         width = _width;
         val = _val;

         update();
	}
};

// объединить 2 дерева
treap_node* merge(treap_node *l, treap_node *r)
{
        if (l == NULL)
               return r;
        if (r == NULL)
               return l;
        if (l -> y >= r -> y)
        {
                l -> push();
                l -> r = merge(l -> r, r);
                l -> update();
                return l;
        }
        else
	{
                r -> push();
                r -> l = merge(l, r -> l);
                r -> update();
                return r;
         }
}

// Разрезать 1 дерево на 2
void split(treap_node *t, int x, treap_node *&l, treap_node *&r)
{
        if (t == NULL)
        {
              l = r = NULL;
              return;
        }
        t -> push();
        if ((t -> l == NULL ? 0 : t -> l -> x) >= x)
        {
                split(t -> l, x, l, t -> l);
                if (l != NULL)
                         l -> p = NULL;
                t -> update();
                r = t;
                return;
        }
        else if ((t -> l == NULL ? 0 : t -> l -> x) + t -> width <= x)
        {
                split(t -> r, x - (t -> l == NULL ? 0 : t -> l -> x) - t -> width, t -> r, r);
                if (r != NULL)
                      r -> p = NULL;
                t -> update();
                l = t;
                return;
        }
        else
        {
                // Самая интересная часть. Если граница раздела приходится на сам элемент, то нужно распилить сам элемент
                treap_node *t1 = new treap_node(x - (t -> l == NULL ? 0 : t -> l -> x), t -> val);
                treap_node *t2 = new treap_node(t -> width - t1 -> width, t -> val);
                l = merge(t -> l, t1);
                r = merge(t2, t -> r);
                l -> p = NULL;
                r -> p = NULL;
                delete(t);
         }
}


Заметим, что по сравнению с обычными декартовыми деревьями, у нас в структуре появилось свойство width — ширина отрезка, хранимого в нашей текущей вершине. А val — само значение.
Еще одно кардинальное изменение — функция split
else
	{
                // Самая интересная часть. Если граница раздела приходится на сам элемент, то нужно распилить сам элемент
		treap_node *t1 = new treap_node(x - (t -> l == NULL ? 0 : t -> l -> x), t -> val);
		treap_node *t2 = new treap_node(t -> width - t1 -> width, t -> val);
		l = merge(t -> l, t1);
		r = merge(t2, t -> r);
		l -> p = NULL;
		r -> p = NULL;
		delete(t);
	}

Здесь появился еще одна часть if. Которая относится к разделению отрезка на 2 подотрезка. И потом склейка в левую и правую часть.

Функцию merge менять не будем, т. к. в задаче, для которой я это использую на асимптотику это не влияет. Этим я решаю задачу сжатия пространства, и могу отвечать на запросы в онлайн. В принципе, если кому надо, можно переписать и функцию mergeб для объединения двух отрезков. Вместо того, чтобы считывать все возможные данные, задавать фиксированные отрезки, а потом бинпоиском искать нужные номера отрезков, и в дереве отрезков делать нужные изменения, я, за туже асимптотику обхожусь небольшим if. Правда приходится использовать декартовы деревья. Для меня так писать гораздо удобнее и быстрее. Может это кому — нибудь пригодится.
Tags:
Hubs:
+23
Comments2

Articles