← All problems

Unique Values

Codeforces · Round #1093 (Div. 1) · Problem B1/B2 ↗

Problem

This is an interactive problem: your program submits a series of queries to a judge, and gets a result back after each query.

There is a hidden array of 2n+12n+1 items. Each item carries a value from 11 to nn: every value appears exactly twice, except for one special value that appears exactly three times.

On your turn, you can query any set of indices between 11 and 2n+12n+1, and the judge answers with the number of values that appear exactly once among the indices you chose (the "singletons" of your set). The indices you choose do not need to be consecutive.

Your goal is to determine the three positions of the special value.

The problem comes in two versions. In the easy version (B1) you may ask at most 6666 queries. In the hard version (B2) you may ask at most 3333. In both versions, 1≤n≤10001 \leq n \leq 1000.

Initial Observations
  1. Construction.
  2. Subsets.
  3. Positions.
  4. Pairs / parity.
  5. Bit-by-bit.
  6. [Generate and Test].
  7. Strategy.
IdeaQuery a set and its complement

To get a feel for the problem, let's set up a small example to play with, say n=4n = 4:

array:  1 2 3 2 1 4 3 1 4      special value 1, at positions 1, 5, 8
index:  1 2 3 4 5 6 7 8 9

The first thing to look at: what happens if you query a set SS and then query its complement, as in everything that's not in SS?

For any of the paired values, one of three things is true:

  • Both copies are in SS. Then they're a pair inside SS, so they contribute 00 to that query, and they don't appear in the complement at all, so they contribute 00 there too.
  • Both copies are outside SS. Then they don't appear in SS at all, so they contribute 00 there, and they form a pair in the complement, which also contributes 00.
  • The pair is split, one copy on each side. Then it shows up as a singleton in SS, contributing 11, and it also shows up as a singleton in the complement, contributing 11 over there.

So in every case, a paired value contributes exactly the same amount to both queries. The special value is where it gets interesting:

  • One copy in SS, two out: a singleton inside (+1+1), and a pair outside (00).
  • Two copies in, one out: a pair inside (00), and a singleton outside (+1+1).
  • All three in, or all three out: all three copies sit on one side, where they count for nothing, and the other side has none of them. 00 to both.
Observation.

Compare the two answers. If they differ, they differ by exactly 11, and whichever side got the bigger answer holds exactly one of the three special positions (the other side holds two). If they're equal, then either all three are in SS or none are.

So how do we tell three apart from zero? Let's write out some queries on the example and look.

Write It Out

Querying some sets that contain all three special copies, next to some sets that don't:

S (indices)      values in S     answer   |S|    parities?
{1,5,8}          1 1 1           0        3      differ (even vs odd)
{2,1,5,8}        2 1 1 1         1        4      differ
{2,4,1,5,8}      2 2 1 1 1       0        5      differ
{2,3}            2 3             2        2      match
{1,2}            1 2             2        2      match
{1,5,2}          1 1 2           1        3      match

Interesting. In the rows where all three special copies are inside SS, the parity of the answer is different from the parity of ∣S∣|S|, and in every other row they are the same. Let me convince myself. Every paired value contributes to ∣S∣|S| and to the answer with the same parity: both copies in adds 22 to the size and 00 to the answer (both even), a split adds 11 and 11 (both odd), both out adds 00 and 00. And the special value with tt copies in SS adds tt to the size, but adds to the answer only when t=1t = 1. So for t=0,1,2t = 0, 1, 2 the two parities track each other, and t=3t = 3 is the one case that pushes them apart: three to the size, nothing to the answer.

So when the answer for both queries is the same, we can use the parity to tell whether it's all three or none. Putting it all together:

Key Observation

For any set SS, querying SS and then querying its complement tells you exactly whether 00, 11, 22, or 33 of the special positions are in SS. If the answers differ by one, the side with the bigger answer holds exactly one of them (and the other side holds two). If the answers are equal, it's all three or none, and comparing the parity of the answer with the parity of ∣S∣|S| settles which.

Written out as pseudo-code:

count(S):                        # how many special positions are in S
    a = query(S)
    b = query(everything not in S)
    if a == b + 1: return 1
    if b == a + 1: return 2
    # a == b: zero or three -- decide by parity
    return 3 if (a mod 2) != (|S| mod 2) else 0

This felt really powerful, but I didn't really know what to do with it yet.

IdeaBinary search on prefixesAC

Since counting is now easy (Key Observation 1: two queries tell us exactly how many special positions are in any set), here's an idea: find the smallest region that contains at least one special position, and shrink it to pinpoint one exactly.

Key Observation

Think prefixes. Let f(i)f(i) be the number of special positions in {1,…,i}\{1, \ldots, i\}, which we can compute with two queries. ff is monotone in ii, so binary search: the smallest ii with f(i)≥1f(i) \geq 1 is the first special position, the smallest with f(i)≥2f(i) \geq 2 is the second, and the smallest with f(i)≥3f(i) \geq 3 is the third.

There are 2n+1≤20012n + 1 \leq 2001 positions, so each binary search takes about log⁡22001≈11\log_2 2001 \approx 11 steps, and each step costs two queries. That's 2222 queries per special position, 6666 in total, which fits in the budget for B1.

Algorithm
# count(S) is the two-query counter from Idea 1
 
for t = 1, 2, 3:
    binary search the smallest i in [1, 2n+1] with count({1..i}) >= t
    the t-th special position is that i

Accepted for B1 (my submission). But 6666 queries is double the budget for B2, so we need something better.

IdeaBit by bit?No Solution

I kept going back to a bit-by-bit idea. The positions are 1111-bit numbers, so what if, for each bit dd, I figure out which of the three special positions have bit dd on?

The query for that: take the set of all indices with bit dd on, plus its complement, and use the counter from Idea 1. If the count is 33, all three positions have the bit on. If it's 00, all three have it off. Otherwise the bit splits them, and now you're into case analysis about which item has which bit.

Working from the most significant bit down, you can treat the three items as sorted while you discover them, filling in a little grid of bits with three columns and 1111 rows. As long as every bit comes out 00-or-33, the three items are still indistinguishable from each other, so on the first split there is nothing to figure out: assign the bits in sorted order, for free.

count() per bit, filling the grid from the most significant bit down:
 
           item1  item2  item3    count   what happens
bit 10:      0      0      0        0     all off; still indistinguishable
bit  9:      1      1      1        3     all on; still indistinguishable
bit  8:      0      0      1        1     first split: assign in sorted order
                                          for free; item 3 is now distinct
bit  7:      ?      ?      ?        1     ambiguous: the lone 1 could be on
                                          item 3, or on one of items 1, 2

That last row is the problem. Once some item is distinguished, a split bit no longer assigns itself: we have to find out which group the lone 11 belongs to, and that takes one more count(). The trick for the extra query is to use the known bit-prefixes: in the grid above, item 3 is the only item whose bits so far are 011011, so a query restricted to indices starting with 011011 is guaranteed not to contain items 1 and 2.

extra count() on { indices with bits 10..8 = 011, bit 7 = 1 }:
    1  ->  item 3 has bit 7 = 1; items 1 and 2 get 0 0
           (those two are still indistinguishable from each other)
    0  ->  item 3 has bit 7 = 0; one of items 1, 2 has the 1, and those
           two are still interchangeable, so assign in sorted order for
           free: they get 0 1 -- and now all three items are distinct

So it's a little state machine: at each bit, the three items are either all still indistinguishable, or one is distinguished from the other two, or all three are distinct, and the state decides whether a split bit assigns itself for free or costs an extra count(). I convinced myself that some variant of this works out to at most four queries per bit: 4444 in total. I'll admit I never wrote it out fully.

But 4444 is still not 3333. And it was clear that any solution leaning on count() automatically pays two queries for each thing it learns, which really eats up the budget. I got stuck here.

IdeaLeverage the all-three caseAC
Key Question

What can I figure out in ONE query, instead of two? As long as every piece of information costs two queries through count(), we are not going to fit in 3333.

Examine Examples

A learning point from previous analyses is to vary the examples slowly, one small change at a time, and watch how the answer moves. So let's take a single query and grow the set by one index at a time, on the same array as before:

array:  1 2 3 2 1 4 3 1 4      special value 1, at positions 1, 5, 8
index:  1 2 3 4 5 6 7 8 9
 
S (indices)      values in S     answer
{2}              2               1
{2,4}            2 2             0       down by 1 (completed a pair)
{2,4,1}          2 2 1           1       up by 1 (new singleton)
{2,4,1,5}        2 2 1 1         0       down by 1 (completed a pair)
{2,4,1,5,8}      2 2 1 1 1       0       unchanged!

Adding an index either brings a net-new value into SS, and the answer goes up by one, or it completes a pair that's already in there, and the answer goes down by one. The last row is the odd one out: adding the third copy of the special value turned a pair into a triple, and neither of those counts for anything, so the answer didn't move.

Observation.

Adding one item to SS changes the answer by +1+1 or −1-1, except in one case: adding the third copy of the special value changes it by 00.

I'll note that at this point I didn't know how to use this, but it felt very worthwhile.

The other thing to try is going back to the case analysis we already have from Idea 1.

Key Question

Is there any sub-case of count() that we could have learned with one query instead of two?

Go through the branches of count(). The first two, where the answers differ by one, genuinely compare the two answers, so they need both queries. But look at the last branch: to separate three from zero, it checks the parity of the answer against the parity of ∣S∣|S|, and ∣S∣|S| is something we already know without asking the judge.

Observation.

The all-three-versus-zero branch never actually uses the complement's answer at all. So maybe the all-three case can be detected with a single query?

Let's check that against the +1/−1/0+1 / -1 / 0 behavior. Start from the empty set: the answer is 00 and the size is 00, so the parities match. Now add items one at a time. Each addition grows the size by exactly 11 and moves the answer by +1+1 or −1-1, so both parities flip together and stay in sync. The only way they can fall out of sync is an addition that leaves the answer alone, and we just saw there is exactly one of those: the third copy of the special value. And once that happens, the ±1\pm 1 tracking picks right back up, so the mismatch never repairs itself.

Lemma.

For any set SS, a single query tests whether all three special positions are in SS:

query(S)≢∣S∣(mod2)  ⟺  all three special positions are in S.\text{query}(S) \not\equiv |S| \pmod 2 \iff \text{all three special positions are in } S .

In all the other cases (00, 11, or 22 of them in SS) the parities match, so this test can't tell those apart. But it's partial information from a single query, and that's new for us.

Lesson

I could have gotten here much sooner. The key question was already written down, and the answer was already sitting in the two-query case analysis; if I had gone back over what I'd learned and checked which cases were deducible with one query instead of two, I would have made much quicker progress. I kept getting distracted by the bit-by-bit idea (Idea 3) instead. When a key question is written down, work it directly.

So we have a new tool: a single query that checks whether all three special positions are in a set. Let's call it the "all-three test". The question is whether this test alone can pin down the positions. And it can, with the same prefix trick as B1:

Key Observation

First, binary search the smallest ii such that {1,…,i}\{1, \ldots, i\} passes the all-three test; that ii is the largest special position. Now pin it: search prefixes of {1,…,i−1}\{1, \ldots, i-1\}, but always toss the found position into the queried set. The all-three test then fires exactly when the remaining two special positions are in the prefix, so the smallest passing prefix ends at the second-largest special position. Pin both and search once more for the first. Monotone every time, one query per step.

Three binary searches, 1111 steps each, one query per step: 3333 queries.

Algorithm
all_three(S):                          # single query
    return (query(S) mod 2) != (|S| mod 2)
 
found = {}                             # special positions discovered so far
mx = 2n + 1
repeat 3 times:
    binary search the smallest k in [1, mx] with
        all_three({1..k} ∪ found)
    found.insert(k)
    mx = k - 1
output "!" found                       # the three positions

Accepted for B2 (my submission).

Review

The solution is all about counting: we can use judge queries to find out how many of the three special positions are inside a chosen set SS. An exact count costs two queries, but just checking whether all three are in the set costs only one. And we can binary search on prefixes, using these counts, to pick out where the special positions are. The path:

  1. Query a set and its complement. Every paired value contributes the same amount to both answers; the special value adds one to whichever side holds exactly one of its copies, and nothing when all three copies are on the same side. So answers differing by one locate a side with exactly one special position; equal answers mean all-three-or-none, and comparing the answer's parity with ∣S∣|S|'s settles which. Two queries count exactly how many of the three special positions are in SS. [1]
  2. Binary search on prefixes (solves B1). The count over {1,…,i}\{1, \ldots, i\} is monotone in ii: find the smallest ii where it reaches 11, then 22, then 33; those are the three positions. Three searches, 1111 steps each, two queries per step: 6666. [2]
  3. Checking for all three costs only one query. Adding an index to SS moves the answer by ±1\pm 1, except the third copy of the special value, which moves it by 00. That's the only way the parities of the answer and ∣S∣|S| can fall out of sync, so query(S)≢∣S∣(mod2)\text{query}(S) \not\equiv |S| \pmod 2 iff all three special positions are in SS. [KQ] [Lemma 1]
  4. Pin and search again (solves B2). Binary search the smallest prefix that contains all three special positions, one query per check; that prefix ends at the largest special position. Add it to every subsequent query set and search for the second; pin both and find the first. Three searches, one query per step: 3333. [3]

(My accepted B1 and B2 submissions.)

References

Problem-solving techniques used:

Examine Examples

Vary the examples slowly; this has been a constant learning point from the last few contests. Both of the key facts here came from watching one query's answer as the set changes by a single item: the case analysis for a set versus its complement, and the +1/−1/0+1 / -1 / 0 behavior that became the one-query test.

Exploit the Constraints

Going back to what I already knew about the problem. The one-query test wasn't new information: it was sitting inside the two-query case analysis the whole time, in the all-three branch, which never actually used the complement's answer. When the key question "what can I learn in one query?" showed up, the right move was to walk back through the cases I'd already worked out and check which of them survive on a single query.

Write It Out

The parity arguments are the kind of thing you have to write out to see. The tally of contributions per value type is three lines once it's on paper, and the B2 solution comes right out of it.

Learning points:

Topics: