Basecamp
Статьи5 месяцев назад

XOR Basis with deletions

Preface

This article uses terms from linear algebra. If you understand them, you can skip this section.

Introduction

Let's recall the following well-known problem:

You can learn more about how to solve this problem here.

In this article, we will consider a more complex problem where there are also deletion queries of integers from the set . Moreover, we receive the next query only after answering the previous one (i.e., the problem needs to be solved "online").

The initial problem can be solved in per query, but we will learn to solve the problem with deletions in .

Structure

In the problem with deletions, uncertainties begin when the integer we add to the XOR basis is represented by s of existing integers of basis.

We want to effectively store each integer that is added to , so we will maintain a sequence () of XOR basises (each of which is initially empty), instead of a single basis.

Adding a integer to

Let's consider how we will add a new integer to the set :

  1. We will gradually iterate over our basises starting from the first. Let be the integer of the current basis we are considering;

  2. If , we simply add to and finish the procedure;

  3. Otherwise, , so we can't add it to the basis, but we want to store this integer somewhere. Therefore, we increment by and return to step .

In other words, we find such minimal that and add to . If we implement this algorithm directly, in the worst case it will work in , which is quite inefficient.

We will improve this algorithm. First, let's consider some properties of this sequence of bases :

Using these two properties, we can improve the algorithm so that addition works in or even .

Recall that we find such minimal that and add to . Since there are no more than unique , we need only to iterate over those , for which or . Then we achieve complexity . If we use binary search over these indices , we achieve complexity .

Thus, we have learned how to add a integer to the set .

Deleting a integer

Choose such that . If we simply delete from , the properties we noted earlier will be violated. We aim to ensure that these properties hold after deletion.

We will delete in the following way:

  1. While , we swap the bases and () and increment by . Since , it does not matter in what order these basises are located in the sequence, so we can rearrange them arbitrarily;

  2. Now . We have two cases:

    1. . In this case, we can simply delete the integer from and finish the procedure;

    2. . Consider the following statement:

      We find this from the statement. Then we can replace in with and now continue the procedure from step , but now we will be deleting the integer from the set .

Let's analyze the time complexity of the deletion procedure. How many times will we encounter case ? It is easy to understand that no more than times, since each time we return to step , the size of our current basis decreases by at least .

Steps can be done directly in :

  1. Completely rebuild the XOR basis after deleting the element in ;

  2. Add elements from the basis to in to find or ensure that it does not exist.

Since we can do these steps at most times in the worst case, deletion will work in .

In fact, we can perform steps in .

Before moving on to optimization, let's consider how we represent the XOR basises so that we can work with them efficiently.

Let and be XOR basises that are built using the algorithm above. A XOR basis represented in such a way has several important properties and consequences:

Let's consider how exactly to delete a integer from the basis and rebuild it in :

  1. Each integer in the basis also has a , which denotes the linear combination of the initial basis elements used to obtain this basis element .

  2. We need to ensure that the integer is no longer present in the linear combinations, and at the same time, the basis remains correctly constructed (the property we mentioned above is preserved).

  3. It turns out that this is quite simple to do. Namely, we find such a integer in the basis that contains in the linear combination and among such integers has the smallest highest bit.

  4. Then we delete this found integer from the basis and modify other integers in the basis that contained in the linear combination by xoring the corresponding integer with . Thus, the highest bit in these integers will not change, so the property is preserved.

  5. Simultaneously, we calculate the integer that contains all the highest bits of the basis integers that contained the integer in the linear combination.

int rebuild_and_delete (int id) {
    int pos = 0, mn = inf, p2 = 0;
    for (int i = 0; i < basis.size(); i++) {
        if (basis[i].id == id) {
            pos = i;
        }
    }
    int bits = 0;
    for (int i = 0; i < basis.size(); i++) {
        if (basis[i].mask & (1 << pos)) {
            if (mn > basis[i].high) {
                mn = basis[i].high;
                p2 = i;
            }
            bits ^= 1 << basis[i].high;
        }
    }

    if (p2 != pos) {
        swap(basis[p2].id, basis[pos].id);
        for (auto &i : basis) {
            swap_bits(i.mask, pos, p2); // just swaps pos-th and p2-th bit in i.mask
        }
        pos = p2;
    }
    for (int i = 0; i < basis.size(); i++) {
        if (i != pos) {
            if (basis[i].mask & (1 << pos)) {
                basis[i].val ^= basis[pos].val;
                basis[i].mask ^= basis[pos].mask;
            }
        }
    }
    int good = (1 << pos) - 1;
    int other = ((1 << len(basis)) - 1) ^ (good | (1 << pos));
    basis.erase(basis.begin() + pos);
    for (auto &i : basis) {
        i.mask = ((i.mask & other) >> 1) | (i.mask & good);
    }
    return bits;
}

Next, we need to find from Statement in or say that it does not exist. For , the following condition must hold: . We know which highest bit disappeared when deleting the integer from . Therefore, when adding to , must "be responsible" for this disappeared bit.

Then, we can check for each in whether it is suitable as . It is not difficult to verify that is suitable if and only if , where is a function that returns the integer of set bits in the integer.

int get_the_same_high_bit (int bits, vector <int> &val) {
    for (auto &i : basis) {
        if (__builtin_popcount(val[i.id] & bits) & 1) {
            return i.id;
        }
    }
    return -1;
}

Thus, we have learned to delete in .

Here is full version of the code:

P. S. Similarly to adding, we can try to use binary search to do deletion: find maximum that . But after moving an integer from to the property of sequence of basises can still not hold. And thus using binary search doesn't improve time complexity.

For some time I thought that we can do better than , but didn't manage to improve. The reason why is that using this property we can construct a sequence of integers , such that for all . Having it, we can actually do additions in , but still can't figure out how to optimize deletions.


35

Комментарии

Загрузка

Момент, получаем данные с сервера