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

Операция set

Чтобы изменить элемент массива $a_k = x$ мы как обычно не можем просто перезаписать $a_k$, ведь нам нужно сохранить все предыдущие версии $k$-й ячейки, поэтому в ячейке $a_k$ мы будем хранить список из всех версий $a_k$. Таким образом, при операции set мы всего лишь добавляем новый элемент в конец списка версий $a_k$. Такой подход называется fat node - то есть в вершине структуры мы храним все версии элемента.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct Node {
    int value;
    int version;
};

struct PersistentArray {
    vector<vector<Node>> versions;
    int version = 0;

    PersistentArray(int n) {
        versions.resize(n);
    }

    void set(int x) {
        versions[x].push_back({x, version});
        version++;
    }
}

Операция get

При операции get version i мы пойти в ячейку $a_i$ и взять оттуда самую посленюю версию до $i$-й включительно, например если $a_i$ менялся на 2-й и 5-й версиях, то при запросе 3-й версии мы вернем значение после 2-й. Чтобы найти подходящую версию нужно лишь запустить бинарный поиск по списку. Таким образом, мы сможем за логарифмическое время находить нужную версию.

1
2
3
4
5
6
7
8
int PersistentArray::get(int i, int version) {
    auto it = lower_bound(versions[i].begin(), versions[i].end(), version, [](Node a, int b) {
        return a.version < b;
    });
    if (it == versions[i].begin()) return -1;
    it--;
    return it->value;
}

Время работы и память

Мы потратили $O(q)$ дополнительной памяти на хранение всех версий массива, где $q$ - количество версий массива, а так же теперь get будет работать за $O(\log q)$. При этом set будет работать за $O(1)$ в среднем, так как мы просто добавляем элемент в конец списка версий. Но, к сожалению, данный подход не позволяет нам сделать массив полностью персистентным, однако позже мы всё-таки придумаем как это сделать.