Blends off the blotter
Count the distinct blends readable off a strip of scent dabs when repeats collapse, by keeping one count for each note rather than listing choices.
A perfumer lays a strip of dabs in a fixed order and reads blends off it. Two different handfuls of dabs can spell the same blend, and the house counts blends.
The problem
The blotter carries a row of dabs, each a single note written as one letter — r
rose, j jasmine, c cedar, v vetiver. The order on the strip is fixed.
A blend is made by choosing at least one dab and reading the chosen notes off in
strip order. Two blends are the same blend when they read the same: on the
strip r j r the first and third dabs both read as r, so those two choices are
one blend, not two.
Count the distinct blends the strip can produce, modulo 1,000,000,007.
Input. dabs — a non-empty string of lowercase letters, the notes in strip
order.
Output. The number of distinct non-empty readings, modulo 10⁹ + 7.
Example.
dabs = "rjc" -> 7
All three notes differ, so every non-empty choice reads differently: r, j,
c, rj, rc, jc, rjc.
A second example, where a repeat collapses two choices into one:
dabs = "rjr" -> 6
Seven choices, but dab 1 and dab 3 both read as r. Everything else is distinct:
r, j, rj, rr, jr, rjr.
A third example, all one note:
dabs = "rrr" -> 3
Only the length survives: r, rr, rrr.
Constraints.
1 <= len(dabs) <= 2 * 10^5- every character is a lowercase letter
- report the count modulo 1,000,000,007
Hints
Hint 1
If every note were different the answer would be 2ⁿ − 1. Work out what a repeated
note costs, on rjr first.
Hint 2
Sort the blends by the note they end on. Where does a blend ending in c come
from?
Hint 3
When a second c is dabbed, every blend that ended at the earlier c can be read
again ending at the new one. The group for c is replaced, not added to.
Approach
Brute force
List every choice of dabs, read each off, and drop repeats with a set: 2ⁿ readings built and stored. Twenty dabs is a million strings; the strip holds two hundred thousand.
The insight
Group the blends by their last note: dabbing a note replaces that group with "every blend so far, extended by this dab, plus the dab alone", and the replacement is exactly where the duplicates go.
A blend ending in r is some earlier blend, or nothing at all, with an r on the
end. When a new r arrives, every reading that ended at an older r can be made
again by ending at this one, so the new group contains the old rather than
adding to it. That keeps the count exact: each reading is counted once, in the
group of its final note — twenty-six numbers, whatever the strip's length.
Algorithm
- Keep
total, the distinct blends so far, andending[note]for each note, all starting at zero. - For each dab of note
c, computefresh = total + 1— every blend so far extended by this dab, plus the dab on its own. - Set
total = total - ending[c] + fresh; the old group forcis superseded. - Set
ending[c] = fresh. - Return
totalmodulo 10⁹ + 7.
Complexity
Time O(n) — one dab, a fixed amount of arithmetic. Space O(k) for the alphabet: twenty-six counters plus the running total, independent of the strip.
Solution
"""Blends off the blotter — count the distinct orders a strip of notes can yield."""
MOD = 10 ** 9 + 7
def solve(dabs):
total = 0 # distinct non-empty blends from the strip read so far
ending = {} # ending[note] = how many of those finish on that note
for note in dabs:
# every blend so far, extended by this dab, plus the dab on its own;
# those are exactly the blends ending on this note now, so the old
# count for the note is replaced rather than added to
fresh = (total + 1) % MOD
total = (total - ending.get(note, 0) + fresh) % MOD
ending[note] = fresh
return totalThe cases that ran
TESTS = [
(("rjc",), 7),
(("rjr",), 6),
(("rrr",), 3),
(("jcvr",), 15),
(("abcdefghijklmnopqrstuvwxyz",), 67108863),
(("ab" * 100,), 407981058),
(("v",), 1),
]Pitfalls
- Answering 2ⁿ − 1. That counts choices of dabs, not readings; with a repeated
note it overshoots —
rrrgives 7 that way and 3 in truth. - Adding rather than replacing.
total += freshcounts every blend ending on a repeated note twice, turningrjrinto 7. - Reducing before subtracting. Once both are taken modulo 10⁹ + 7,
total - ending[c]can come out negative; add the modulus back before reporting, in any language whose%follows the sign of the left side.
Variants
- Two hydrophones — subsequences again, matched against a second sequence instead of counted.
- Burns and vents — another counting DP whose state is what has been reached, not which choices got there.