public void add(T elem) {
        /* Inserting into an IntervalHeap works by inserting into
         * the last node (if possible), or adding a new singleton node
         * if need be.  Once that's done, it is inserted either into
         * the min-heap or max-heap as appropriate, completely ignoring
         * the other half of the heap.
         */
        if (elem == null)
            throw new NullPointerException("IntervalHeap does not store null values.");

        /* Determine whether the node should go in its own node, or in the unused space
         * in the singleton node at the end.
         */
        if (size() % 2 == 0) // New node
            elems.add(new Node<T>(elem, null));
        else { // Unused space
            Node<T> currNode = elems.get(elems.size() - 1); // Last node in the tree

            /* Determine whether the value goes in the low or high slot based
             * on how it compares to the singleton.
             */
            if (comparator.compare(currNode.low, elem) > 0) { // Goes in low slot
                /* Move the singleton to the high slot. */
                currNode.high = currNode.low;
                currNode.low = elem;
            } else // Goes in high slot
                currNode.high = elem;
        }

        /* Bump the element count to ensure we track size correctly. */
        ++numElems;
        
        /* Determine whether to do a min-heap or max-heap insert.  However, we can
         * only decide this if there's a parent node, since otherwise there's nothing
         * to compare against.
         */
        if (size() <= 2)
            return;
        
        /* If the node is less than the low element of its parent, do a min-heap
         * insert since it can't exceed the parent's upper-bound.
         */
        Node<T> parent = elems.get((elems.size() - 1) / 2);
        if (comparator.compare(parent.low, elem) > 0)
            minHeapInsert();
        /* Otherwise, if it's bigger than the high element of its parent, do a
         * max-heap insert since it can't be lower than the parent's
         * lower-bound.
         */
        else if (comparator.compare(parent.high, elem) < 0)
            maxHeapInsert();
        /* Otherwise, the node is in the right place. */
    }