D - Linear Probing Editorial by en_translator
Naive solution
Suppose that we naively increment \(h\) step-by-step for every query of \(t_i = 1\). This method may cost \(\Theta (Q^2)\), for example for the following query:
- For every query, \(t_i = 1\) and \(x_i \bmod N = 0\).
Solution 1
The set of \(i\) such that \(A_i = -1\) can be expressed as the union of \(O(Q)\) number of segments. Assume that the segments can be expressed as \([l_1, r_1), [l_2, r_2), \dots, [l_n, r_n)\), where \(l_1 \lt r_1 \lt l_2 \lt r_2 \lt \dots \lt l_n \lt r_n\).
For \(0 \leq h \lt N\), consider incrementing \(h\) one by one until it becomes \(A_{h \bmod N} = -1\); let \(p(h)\) be first such \(h \bmod N\).
- If \(r_n \leq h\), then \(p(h) = l_1\).
- If \(h \lt r_n\), let \(m\) be the minimum \(k\) such that \(h \lt r_k\), then:
- if \(l_m \leq h\), then \(p(h) = h\);
- if \(h \lt l_m\), then \(p(h) = l_m\).
For the query of \(t_i = 1\), denoting \(k = p(x_i \bmod N)\), we can let \(A_k \leftarrow x_i\). Then, we need to update \([l_1, r_1), [l_2, r_2), \dots, [l_n, r_n)\) as follows:
- Take the unique segment \(l_m \leq k \lt r_m\) and remove \([l_m, r_m)\).
- If \(l_m \lt k\), then add \([l_m, k)\) anew.
- If \(k + 1 \lt r_m\), then add \([k + 1, r_m)\) anew.
Adding/removing segments and finding minimum \(k\) such that \(h \lt r_k\) can be done in an \(O(\log Q)\) time with data structures like balanced binary search tree or Fenwick tree. In C++, we may use std::set
or std::map
.
If we manage all elements with an array, we can read and write the values of \(A_i\) in a total space complexity of \(\Theta (N)\) and in an \(O(1)\) time per query; if we use dictionary types to store only elements with \(A_i \neq -1\), it will cost a total space complexity of \(O(Q)\) and a time complexity of \(O(1)\) or \(O(\log Q)\) per query.
In summary, the problem has been solved in a total of \(O(N + Q\log Q)\) or \(O(Q\log Q)\) time, the latter of which is independent of \(N\).
Solution 2
For \(0 \leq h \lt N\), consider incrementing \(h\) one by one until it becomes \(A_{h \bmod N} = -1\); let \(p(h)\) be first such \(h \bmod N\).
Prepare an auxiliary array \(P = (P_0, \dots, P_{N - 1})\) initialized with \(P_h = h\), which we use to find \(p(h)\).
In order to find \(p(h)\), we do as follows.
- While \(A_h \neq -1\), repeat assigning \(h \leftarrow P_h\).
Also, when updating an element of \(A_k = -1\) with a value other than \(-1\), we update as \(P_{k} \leftarrow p(k + 1)\).
Did you recognize that the operations above are similar to the data structure called Union-Find? We can sue a method called coordinate compression to process each operation in amortized \(O(\log N)\) time.
In summary, the problem has been solved in a total of \(O(N + Q\log N)\) time.
By the way
This problem is based on Linear probing in Open addressing, which is a method of implementing Hash tables.
As we introduced in the “Naive solution” section, if the distribution of \(x_i \bmod N\) is biased, then it may require more steps to process. In order to accomplish fast hash table, it is crucial to use a hash function that yields uniform values.
posted:
last update: