**This is a guest post by Mikkel Thorup:**

--------------------------

I think there is nothing more inhibiting for problem solving than referees looking for new general techniques.

When I go to STOC/FOCS, I hope to see some nice solutions to important problems and some new general techniques. I am not interested in semi-new techniques for semi-important problems. A paper winning both categories is a wonderful but rare event.

Thus I propose a max-evaluation rather than a sum. If the strength of a paper is that it solves an important problem, then speculations on the generality of the approach are of secondary importance. Conversely, if the strength of the paper is some new general techniques, then I can forgive that it doesn't solve anything new and important.

One of the nice things about TCS is that we have problems that are important, not just as internal technical challenges, but because of their relation to computing. At the end of the day, we hope that our techniques will end up solving important problems.

Important problems should be solved whatever way comes natural. It may be deep problem specific understanding, and it may build on previous techniques. Why would we be disappointed if an old problem got solved by a surprising reuse of an old technique?

## Wednesday, September 29, 2010

### Problem solving versus new techniques

Posted by Mihai at 4:47 PM 11 comments Links to this post

## Monday, September 27, 2010

### Retrieval-Only Dictionaries

We saw two cool applications of dictionaries without membership; now it's time to construct them. Remember that we are given a set *S*, where each element *x*∈*S* has some associated data[*x*], a *k*-bit value. We want a data structure of O(*nk*) bits which retrieves data[*x*] for any *x*∈*S* and may return garbage when queried for *x*∉*S*.

*n*], B[1..2

*n*] storing keys, and places a key either at A[

*h*(

*x*)] or B[

*g*(

*x*)]. Instead of this, our arrays

*A*and

*B*will store

*k*-bit values (O(

*nk*) bits in total), and the query retrieve-data(

*x*) will return A[

*h*(

*x*)]

**xor**B[

*g*(

*x*)].

*x*∈

*S*returns data[

*x*] correctly. This is a question about the feasibility of a linear system with

*n*equations (one per key) and 4

*n*variables (one per array entry).

*x*] and the parent node has been fixed already. As the component is acyclic, there is only one constraint on every new node, so there are no conflicts.

**xor**all cycle nodes by some Δ, the answers are unchanged, since the Δ's cancel out on each edge. So a cycle of length

*k*must output

*k*independent data values, but has only

*k*-1 degrees of freedom.

- the graph is acyclic with some constant probability. Thus, the construction algorithm can rehash until it finds an acyclic graph, taking O(
*n*) time in expectation. - the total length of all cycles is O(lg
*n*) with high probability. Thus we can make the graph acyclic by storing O(lg*n*) special elements in a stash. This gives construction time O(*n*) w.h.p., but the query algorithm is slightly more complicated (for instance, it can handle the stash by a small hash table on the side).

*k*allows a saving of roughly

*k*bits in the encoding: we can write the

*k*keys on the cycle (

*k*lg

*n*bits) plus the

*k*hash codes (

*k*lg(2

*n*) bits) instead of 2

*k*hash codes (2

*k*lg(2

*n*) bits).

**Further remarks.**Above, I ignored the space to store the hash functions

*h*and

*g*. You have to believe me that there exist families of hash functions representable in O(

*n*

^{ε}) space, which can be evaluated in constant time, and make cuckoo hashing work.

*kn*bits. As far as I know, the state of the art is given by [Pagh-Dietzfelbinger ICALP'08] and [Porat].

Posted by Mihai at 2:17 PM 2 comments Links to this post

## Tuesday, September 21, 2010

### Static 1D Range Reporting

Method 4 for implementing van Emde Boas with linear space, described in my last post, is due to [Alstrup, Brodal, Rauhe: STOC'01]. They worked on static range reporting in 1 dimension: preprocess a set of integers *S*, and answer query(*a*,*b*) = report all points in *S* ∩ [*a*,*b*]. This is easier than predecessor search: you can first find the predecessor of *a* and then output points in order until you exceed *b*. Using van Emde Boas, we would achieve a linear-space data structure with query time O(lglg *u* + *k*), where *k* is the number of points to be reported.

Static 1D range reporting can be solved with O(n) space and O(1+k) query time.

**The solution.**We need a way to find

*some*(arbitrary) key from

*S*∩ [a,b] in constant time. Once we have that, we can walk left and right in an ordered list until we go outside the interval.

*n*lg

*u*) space; this was described by [Miltersen, Nisan, Safra, Wigderson: STOC'95]. Of course, we build the trie representing the set. Given the query [

*a*,

*b*] let us look at the lowest common ancestor (LCA) of

*a*and

*b*. Note that LCA(

*a*,

*b*) is a simple mathematical function of the integers

*a*and

*b*, and can be computed in constant time. (The height of the LCA is the most significant set bit in

*a*xor

*b*.)

- if LCA(
*a*,*b*) is a branching node, look at the two descendant branching nodes. If the interval [*a*,*b*] is nonempty, it must contain either the max in the tree of the left child, or the min in the tree of the right child. - if LCA(
*a*,*b*) is an active node, go to its lowest branching ancestor, and do something like the the above. - if LCA(
*a*,*b*) is not an active node, the interval [*a*,*b*] is certainly empty!

*a*,

*b*)

__assuming__that LCA(

*a*,

*b*) is active. This is significantly easier than predecessor search, which needs the lowest branching ancestor of an arbitrary node.

*n*lg

*u*) active nodes in a hash table, with pointers to their lowest branching ancestors.

*n*) space is: store only O(

*n*√lg

*u*) active nodes, and store them in a retrieval-only dictionary with O(lglg

*u*) bits per node. We store the following active nodes:

- active nodes at depth i·√lg
*u*; - active nodes less than √lg
*u*levels below a branching node.

*a*,

*b*) in the dictionary. If the lowest branching ancestor is less than √lg

*u*levels above, LCA(

*a*,

*b*) is in the dictionary and we find the ancestor. If not, we truncate the depth of the LCA to a multiple of √lg

*u*, and look up the ancestor at that depth. If [a,b] is nonempty, that ancestor must be an active node and it will point us to a branching ancestor.

Posted by Mihai at 1:20 PM 0 comments Links to this post

### vEB Space: Method 4

In the previous post I described 3 ways of making the "van Emde Boas data structure" take linear space. I use quotes since there is no unique vEB structure, but rather a family of data structures inspired by the FOCS'75 paper of van Emde Boas. By the way, if you're curious who van Emde Boas is, here is a portrait found on his webpage.

*n*-1 branching nodes connected by 2

*n*-1 "active" paths; if we know the lowest branching ancestor of the query, we can find the predecessor in constant time. Willard's approach is to store a hash table with all O(

*n*lg

*u*) active nodes in the trie; for each node, we store a pointer to its lowest branching ancestor. Then, we can binary search for the height of the lowest active ancestor of the query, and follow a pointer to the lowest branching node above. As the trie height is O(lg

*u*), this search takes O(lglg

*u*) look-ups in the hash table.

*n*lg

*u*) to O(

*n*) by bucketing. But let's try something else. We could break the binary search into two phases:

- Find
*v*, the lowest active ancestor of the query at some depth of the form i·√lg*u*(binary search on*i*). Say*v*is on the path*u*→*w*(where*u*,*w*are branching nodes). If*w*is not an ancestor of the query, return*u.* - Otherwise, the lowest branching ancestor of the query is found at some depth in [ i·√lg
*u*, (i+1)√lg*u*]. Binary search to find the lowest active ancestor in this range, and follow a pointer to the lowest active ancestor.

*n*√lg

*u*) active nodes in the hash table! To support step 1., we need active nodes at depths i·√lg

*u*. To support step 2., we need active nodes whose lowest branching ancestor is only ≤ √lg

*u*levels above. All other active nodes can be ignored.

*n*lg

^{ε}

*u*) by breaking the search into more segments. But to bring the space down to linear, we use heavier machinery:

**Retrieval-only dictionaries.**Say we want a dictionary ("hash table") that stores a set of

*n*keys from the universe [

*u*], where each key has

*k*bits of associated data. The dictionary supports two operations:

- membership: is
*x*in the set? - retrieval: assuming
*x*is in the set, return data[*x*].

*u*choose

*n*) +

*nk*≈

*n*(lg

*u*+

*k*)

*bits: the data structure needs to encode the set itself, and the data.*

*x*) may return garbage if

*x*is not in the set.)

*nk*) bits. I will describe this in the next post, but I hope it is believable enough.

*n*√lg

*u*) from above. We will store branching nodes in a real hash table (there are only

*n*-1 of them). But observe the following about the O(

*n*√lg

*u*) active nodes that we store:

- We only need
*k*=O(lglg*u*) bits of associated data. Instead of storing a pointer to the lowest branching ancestor, we can just store the height difference (a number between 1 and lg*u*). This is effectively a pointer: we can compute the branching ancestor by zeroing out so many bits of the node. - We only need to store them in a retrieval-only dictionary. Say we query some node
*v*and find a height difference δ to the lowest branching ancestor. We can verify whether*v*was real by looking up the δ-levels-up ancestor of*v*in the hash table of branching nodes, and checking that*v*lies on one of the two paths descending from this branching node.

*n*√lg

*u*· lglg

*u*) bits, which is o(

*n*) words of space! This superlinear number of nodes take negligible space compared to the branching nodes.

Posted by Mihai at 11:11 AM 1 comments Links to this post

## Sunday, September 19, 2010

### Van Emde Boas and its space complexity

In this post, I want to describe 3 neat and very different ways of making the space of the van Emde Boas (vEB) data structure linear. While this is not hard, it is subtle enough to confuse even seasoned researchers at times. In particular, it is the first bug I ever encountered in a class: Erik Demaine was teaching Advanced Data Structures at MIT in spring of 2003 (the first grad course I ever took!), and his solution for getting linear space was flawed.

*S*of |

*S*|=

*n*integers from the universe {1, ...,

*u*} and answer:

predecessor(q) = max {x∈S|x≤q}

*u*) time, which is significantly faster than binary search for moderate universes.

*u*segments of size √

*u.*Let hi(

*x*) = ⌊

*x*/√

*u*⌋ be the segment containing

*x*, and lo(

*x*) =

*x*mod √

*u*be the location of

*x*within its segment. The data structure has the following components:

- a hash table
*H*storing hi(*x*) for all*x*∈*S*. - a top structure solving predecessor search among { hi(
*x*) |*x*∈*S*}. This is the same as the original data structure, i.e. use recursion. - for each element α∈
*H*, a recursive bottom structure solving predecessor search inside the α segment, i.e. among the keys { lo(*x*) |*x*∈*S*and hi(*x*)=α }.

*q*) ∈

*H*. If so, all the action is in

*q*'s segment, so you recurse in the appropriate bottom structure. (You either find its predecessor there, or in the special case when

*q*is less than the minimum in that segment, find the successor and follow a pointer in a doubly linked list.)

*q*'s segment is empty, all the action is at the segment level, and

*q*'s predecessor is the max in the preceding non-empty segment. So you recurse in the top structure.

*u*to √

*u*, i.e. lg

*u*shrinks to ½ lg

*u.*Thus, in O(lglg

*u*) steps the problem is solved.

*u*) = 1 + 2 S(√

*u*). Taking logs: S'(lg

*u*) = 1 + 2 S'(½ lg

*u*), so the space is O(lg

*u*) per key.

*n*)? Here are 3 very different ways:

**Brutal bucketing.**Group elements into buckets of O(lg

*u*) consecutive elements. From each bucket, we insert the min into a vEB data structure. Once we find a predecessor in the vEB structure, we know the bucket where we must search for the real predecessor. We can use binary search inside the bucket, taking time O(lglg

*u*). The space is (

*n*/lg

*u*) ·lg

*u*= O(

*n*).

**Better analysis.**In fact, the data structure from above does take O(

*n*) space if you analyze it better! For each segment, we need to remember the max inside the segment in the hash table, since a query in the top structure must translate the segment number into the real predecessor. But then there's no point in putting the max in the bottom structure: once the query accesses the hash table, it can simply compare with the max in O(1) time. (If the query is higher than the max in its segment, the max is the predecessor.)

*u*) copies of each element, so space O(

*n*lglg

*u*).

*u*bits. At the second level, they are only ½ lg

*u*bits; etc. Thus, the cost per key, in bits, is a geometric series, which is bounded by O(lg

*u*). In other words, the cost is only O(1) words per key. (You may ask: even if the cost of keys halves every time, what about the cost of pointers, counters, etc? The cost of a pointer is O(lg

*n*) bits, and

*n*≤

*u*in any recursive data structure.)

**Be slick.**Here's a trickier variation due to Rasmus Pagh. Consider the trie representing the set of keys (a trie is a perfect binary tree of depth lg

*u*in which each key is a root-to-leaf path). The subtree induced by the keys has

*n*-1 branching nodes, connected by 2

*n*-1 unbranching paths. It suffices to find the lowest branching node above the query. (If each branching node stores a pointer to his children, and the min and max values in its subtree, we can find the predecessor with constant work after knowing the lowest branching node.)

- a top structure, with all paths that begin and end above height ½ lg
*u.* - a hash table
*H*with the nodes at depth ½ lg*u*of every path*crossing*this depth. - for each α∈
*H*, a bottom structure with all paths starting below depth ½ lg*u*which have α as prefix.

*p*to the lowest branching node found so far. Initially

*p*is the root. Here is the query algorithm:

- if
*p*is below depth ½ lg*u*, recurse in the appropriate bottom structure. (We have no work to do on this level.) - look in
*H*for the node above the query at depth ½ lg*u.*If not found, recurse in the top structure. If found, let*p*be the bottom node of the path crossing depth ½ lg*u*which we just found in the hash table. Recurse to the appropriate bottom structure.

Posted by Mihai at 2:34 PM 3 comments Links to this post

## Tuesday, September 7, 2010

### IOI Wrap-up

In the past 2 years, I have been a member of the Host Scientific Committee (HSC) of the IOI. This is the body that comes up with problems and test data. While it consists primarily of people from the host country (Bulgaria in 2009, Canada in 2010), typically the host will have a call-for-problems and invite the authors of problems they intend to use.

This year, I was elected member of the International Scientific Committee (ISC). This committee works together with the HSC on the scientific aspects, the hope being that a perenial body will maintain similar standards of quality from one year to another. There are 3 elected members in the ISC, each serving 3-year terms (one position is open each year).

I anticipate this will be a lot of fun, and you will probably hear more about the IOI during this time. When a call for problems comes up (will be advertised here), do consider submitting!

I will end with an unusual problem from this IOI:

Consider the largest 50 languages on Wikipedia. We picked 200 random articles in each language, and extracted an excerpt of 100 consecutive characters from each. You will receive these 10000 texts one at a time in random order, and for each you have to guess its language. After each guess, your algorithm learns the correct answer. The score is the percentage of correct guesses.Considering the tiny amount of training data and the real-time nature of guessing, one might not expect too good solutions. However, it turns out that one can get around 90% accuracy with relatively simple ideas.

To discourage students from coding a lot of special rules, a random permutation is applied to the Unicode alphabet, and the language IDs are random values. So, essentially, you start with zero knowledge.

My own approach was to define

*Score*(

*text*,

*language*) as the minimal number of substrings seen previously in this language that compose the text. This can be computed efficiently by maintaining a suffix tree for each language, and using it to answer longest common prefix queries.

Posted by Mihai at 2:23 PM 10 comments Links to this post