CDQ (after Chen Danqi) divides the sequence of operations in half, recurses on both halves, and lets the left half’s updates act on the right half’s queries. Every update/query pair interacts at exactly one level. This removes one dimension at the cost of a factor, with only memory.
Proof: every (earlier update, later query) pair interacts at exactly one level
Fix an update at position and a query at position with (an update only affects queries later in the fixed first dimension, by the problem’s own ordering). At the root call, either both land in the same half — recursion continues, and by induction the same argument applies one level down with a strictly smaller range — or is in the left half and in the right half, in which case the merge step at this exact call applies ‘s update to ‘s query, and this call never recurses on a range containing both again. So the pair is handled at the unique level where they first separate: not zero times, since a singleton range cannot contain both, forcing eventual separation; not more than once, since once separated into different child calls, no later call’s range contains both again.
3D partial order
For points , count for each point how many points have , , (CSES-style “3D dominance”).
- Sort by (ties broken by , then ). Merge identical points first and give them multiplicities, otherwise equal points count each other inconsistently.
- CDQ on the index: after recursing, merge the halves by . Left elements insert into a BIT, right elements query.
- Undo the BIT insertions (don’t clear the whole BIT).
struct P { int a, b, c, cnt, id; };
vector<P> p; vector<ll> res; BIT bit;
void cdq(int l, int r) { // [l, r), p sorted by a
if (r - l <= 1) return;
int m = (l + r) / 2;
cdq(l, m); cdq(m, r); // both halves are now sorted by b
int i = l;
for (int j = m; j < r; j++) {
for (; i < m && p[i].b <= p[j].b; i++) bit.add(p[i].c, p[i].cnt);
res[p[j].id] += bit.sum(p[j].c);
}
for (int k = l; k < i; k++) bit.add(p[k].c, -p[k].cnt);
inplace_merge(p.begin() + l, p.begin() + m, p.begin() + r, [](auto& x, auto& y) { return x.b < y.b; });
} time, memory. Point also dominates its own duplicates: add cnt - 1 to its answer after merging duplicates.
Proof: time, memory
The recursion itself is standard divide and conquer with (the merge step scans both halves with two pointers, , but each BIT operation inside costs , and there are of them per merge) — by the recursion-tree argument (, on top of the extra factor folded into ), this solves to . Memory is because the BIT is a single array of size shared across all recursive calls (undone after each merge rather than rebuilt), and
inplace_mergeuses extra space (or scratch allocated once, not per call).
Removing time from dynamic problems
Treat the operation index as the first dimension. “Point updates and rectangle-sum queries, interleaved”: updates in the left half of time affect queries in the right half. Inside the merge, sweep by with a BIT over . That gives with memory, instead of a 2D segment tree or BIT of BITs ( memory).
The pattern works whenever:
- contributions are additive (or otherwise mergeable) across updates;
- an update affects a query iff it comes earlier in time and satisfies a static (time-independent) condition.
CDQ for DP
When depends on for with a condition needing a data structure (2D dominance, convex hull with non-monotone slopes), the values aren’t known in time for a simple sweep. Process in-order:
cdq(l, m): left values become final.- Apply the transitions from to (sort both halves, sweep with a structure).
cdq(m, r).
Note the order differs from counting: the right half is recursed after the cross step. Examples: 3D LIS (longest chain in partial order), DP with convex hull trick where slopes and query points come in arbitrary order (build the hull of the left half, query it with the right half’s points sorted).
Higher dimensions
Nest: CDQ inside CDQ removes one more dimension each time, for dimensions. Beyond 4D, bitset approaches (: one bitset per dimension and value threshold, AND them together) or KD-trees are usually simpler.
Proof: nesting gives for dimensions
By induction on . For dimensions, sorting by the first and sweeping with a BIT over the second is with , the base case. For dimensions, outer CDQ on dimension 1 (vs. dimension 2, the merge step) costs where is the cost of resolving the remaining dimensions at each merge — by the inductive hypothesis . Substituting into the recursion-tree bound (ρ = 1 case, since ): each of the depths contributes total work (summed over that depth’s subproblems, since work is linear-ish per level by the standard D&C accounting), giving .
Alternatives
| Problem | CDQ | Other |
|---|---|---|
| 2D dominance (static) | unnecessary | sort + BIT sweep |
| 3D dominance | , memory | BIT of sorted vectors ; KD-tree |
| point update + rectangle query | offline | 2D BIT with compression; online: BIT of treaps or wavelet structures |
| dynamic CHT | CDQ + static hull per level | Li Chao tree (usually easier) |
Pitfalls
- Ties: decide which equal coordinates count as dominated and enforce it in both the initial sort and the merge comparison.
- Undo the structure exactly; clearing an BIT per call makes it .
- Keep the merge stable, and make sure step order (recurse right before or after the cross step) matches counting or DP.
When not to use it
CDQ has a real constant factor and is fiddly to debug. Before writing it, check whether a plain offline sweep with one BIT suffices (two dimensions only), the queries can be sorted to remove a dimension outright, or a persistent segment tree answers it more directly — any of the three is less code than CDQ when it applies.