Introduction
This article records my experience solving Luogu P1637 Increasing Triples and SPOJ INCSEQ Increasing Subsequences, along with a template I happened to discover along the way that solves the general problem of counting strictly increasing subsequences of length k.
The route is roughly as follows:
- Start with P1637 and use two Fenwick trees to express the counting of increasing triples as a very natural process.
- Abstract that implementation into mathematical form and recognize that it is already a DP framework with “layers by length + prefix sums over the value domain.”
- Extend the framework to arbitrary k in SPOJ INCSEQ and turn it into a template ready to drop into a code library.
Part 1: P1637 Increasing Triples (k = 3)
1. Problem Statement
Luogu P1637 Increasing Triples
The key information:
We need to count triples
- , and the value domain is large, so coordinate compression is needed.
The naive approach clearly will not work.
Even an approach that “enumerates the middle index j, then scans left and right” is too slow.
We therefore need something on the order of .
2. The Idea: Divide by “Length,” Rather Than “Left and Right”
Many explanations of increasing triples emphasize:
- The number of elements to the left that are smaller than , denoted .
- The number of elements to the right that are greater than , denoted .
- Answer = .
I did not know this standard approach beforehand, so I did not write it that way. Instead, I maintained separate layers by subsequence length:
- Increasing subsequences of length 1: .
- Increasing subsequences of length 2: , satisfying .
- Increasing subsequences of length 3: , satisfying .
Scan indices pos from left to right. When the current value is a[pos] = p, perform three steps:
- Treat the current value as the third element .
- Count all “previously formed increasing subsequences of length 2 whose ending value is < p.”
- Each can be extended with to form an increasing subsequence of length 3.
- Treat the current value as the second element .
- Count all “increasing subsequences of length 1 whose ending value is < p.”
- Each combines with to form an increasing subsequence of length 2.
- Treat the current value as the first element .
- On its own, it is an increasing subsequence of length 1.
The mathematics behind this can be expressed with two functions:
- : the number of strictly increasing subsequences of length 1 ending with value .
- : the number of strictly increasing subsequences of length 2 ending with value .
For the current value , with compressed index , we have:
The Chinese label in the first line means “the number of new subsequences of length 3.” The overall answer is the sum of all these new counts.
The question becomes how to efficiently compute over the value domain. This is a typical prefix-sum and point-update problem, so a Fenwick tree fits directly.
3. Coordinate Compression and Two Fenwick Trees
3.1 Compressing the Value Domain
Since the values can be large, using them directly as BIT indices is not a sound choice. First compress the coordinates:
- Put every
a[i]intodisperse. - Sort and deduplicate.
- Find each
a[i]indisperseand call its 1-based indexidx.
All subsequent Fenwick-tree indexing uses idx.
3.2 What the Two Fenwick Trees Represent
My code creates two trees:
cnt_op- Maintains , the counts of increasing subsequences of length 1.
- In effect, it counts occurrences of value before the current position.
double_pair- Maintains , the counts of increasing subsequences of length 2.
- Records how many increasing subsequences of length 2 end with value .
Thus:
- is expressed as
query(idx - 1, cnt_op). - is expressed as
query(idx - 1, double_pair).
4. The Scan and Why Forward Updates Are Correct
For each p, with compressed index idx, execute the following in order:
- Use the current element as the third element
1 | ans += query(idx - 1, double_pair); |
- Use the current element as the second element
1 | update(idx, query(idx - 1, cnt_op), double_pair, (ll)disperse.size()); |
- Use the current element as the first element
1 | update(idx, 1, cnt_op, (ll)disperse.size()); |
We scan from left to right and update the “length 2” layer before the “length 1” layer. At first glance, this may raise a concern: could the current element read results that it has just written?
But notice:
- Every
queryusesidx - 1. - The Fenwick implementation ensures that
query(idx - 1)only accesses positions with index < idx. - Meanwhile,
update(idx, ...)writes to nodes with index ≥ idx.
Therefore, whether we have two dimensions—the f1/f2 in P1637—or extend to k dimensions later, forward updates will never read the entry just written by the current element. There is no contamination.
5. Final Accepted Code for P1637
I have kept the code exactly in my own style:
vector + 离散化 + 两棵 Fenwick—a vector, coordinate compression, and two Fenwick trees.
1 |
|
(Some scratch notes from solving the problem.)
Figure legend: “tree (coordinate compression)” labels the compressed value domain. The second tree counts increasing pairs. The notes on the right show the order: count new answers using the pair tree, then update the pair counts using the single-element tree. The examples also work through duplicate values.
That solves P1637, the k=3 case. Next comes the main point of this article: extending this implementation to arbitrary k and making it a template.
Part 2: From P1637 to a General k-Layer Template—Applying It to INCSEQ
This part does three main things:
- Fully abstract the P1637 implementation into mathematical form.
- Extend it to arbitrary length .
- Validate the template on SPOJ INCSEQ, a representative problem, and provide the final code.
1. Abstracting the DP Structure in P1637
First, let us extract the structure from the P1637 implementation.
Over the compressed value domain , for each length , define:
In English, is the number of strictly increasing subsequences of length whose ending value has compressed index .
In P1637:
-
is stored in
cnt_op. -
is stored in
double_pair. - Each scan step produces some new subsequences of length 3 and adds their count to
ans, without explicitly storing .
For the current element value , at index idx, the transitions can be written together as:
If we also choose to store explicitly, then:
This naturally gives a unified formula for any :
This is the core structure: “DP layers by length, with Fenwick prefix sums over the value domain.”
2. Extending to Arbitrary k: The General Recurrence
For the general problem of “counting strictly increasing subsequences of length k,” after processing every element, we want:
The transition rules become:
Each layer has a corresponding Fenwick tree:
The Fenwick tree for layer maintains prefix sums of over the value domain, so
To express this DP in array notation, let:
The same transitions can then be written as:
Use a Fenwick tree to reduce the “prefix sum” on the right to .
3. Why Are Forward Updates Safe Here Too?
Notice the loop order above, corresponding to my INCSEQ code:
1 | for (ll p: a) { |
Unlike many DP problems, this one does not require iterating backward from k → 1. There are two reasons:
- For a fixed
len, we read from the previous layer,dp_tree[len - 1].
That layer has not yet been updated in the loop for the current elementp. - Even where there is a dependency along the length dimension, such as len=3 depending on len=2,
we only queryquery(idx - 1, dp_tree[len - 1]),
while the current element’s new contributions are written only toidxand subsequent nodes.
These new contributions therefore cannot be read.
The structure of a Fenwick tree guarantees:
update(idx, ...)writes only to nodes with index ≥ idx.query(idx - 1)reads only nodes with index ≤ idx - 1.
Therefore, updating forward through len=2..k is completely safe. Each layer always uses “the previous layer’s state before the current element.”
4. Putting the Template into Practice: SPOJ INCSEQ
SPOJ INCSEQ - Increasing Subsequences
Problem outline: Given , , and a sequence of length , count the strictly increasing subsequences of length exactly k, modulo MOD = 5,000,000.
The constraints are approximately:
This matches the model we have abstracted perfectly, making it an excellent template problem.
5. Implementation Details
There are several key points in my accepted code:
5.1 Coordinate Compression
Again, sort and deduplicate coord to map the original values to :
1 | sort(coord.begin(), coord.end()); |
This makes the Fenwick-tree size depend only on size, rather than the original value domain.
5.2 Organizing k Fenwick Layers
Use a two-dimensional array:
1 | vector<vector<ll> > dp_tree(k + 5, vector<ll>(size + 5, 0)); |
Here:
dp_tree[len]is “the internal array of the Fenwick tree for subsequence length len.”dp_tree[len][pos]is an internal Fenwick-tree node representing .
Together with update and query, this forms a structure of k Fenwick layers.
5.3 Modular Arithmetic
All additions in the code use:
1 | tree[i] = (tree[i] + val) % MOD; |
This keeps values within and prevents overflow.
The problem also requires the answer modulo 5,000,000, which is handled directly during every Fenwick update.
5.4 Complexity
For every element p:
- Perform a
query + updatefor eachlen = 2..k, with each taking . - Perform one more
updateforlen = 1.
The overall time complexity is:
With , this is comfortably fast enough.
6. Final Accepted Code for INCSEQ
This is the version accepted on SPOJ, using exactly the template idea developed above.
1 |
|
(Some more scratch notes from solving the problem.)
Figure legend: In tree[len][r], the Chinese labels identify len as the subsequence length and r as the position of the subsequence’s largest element in coord. Rows are length layers and columns are compressed ending values. The left example has n=7, k=4 and answer 4; the right example includes repeated values.
Takeaways: A Result Ready for the Code Library
The main result I want to retain from this article is this set of transitions:
In array notation, let:
Then the equivalent DP formulation is:
Use a Fenwick tree to reduce the “prefix sum” on the right to , and we obtain a general template that:
- Runs in time.
- Counts strictly increasing subsequences for any .
- Is easy to apply to different problems.
P1637 is a “special-case implementation” of this template for .
INCSEQ is a standard template problem that develops the structure in full.
The next time a problem involves “strictly increasing subsequences of length k,” I can modify and extend this code directly rather than derive the approach from scratch.