CF 106193C - Compact Encoding
We are given a non-negative integer that fits into 32 bits, and we need to convert it into a variable-length byte encoding. Instead of always using four bytes, we represent the number using chunks of 7 bits packed into bytes.
Rating: -
Tags: -
Solve time: 47s
Verified: yes
Solution
Problem Understanding
We are given a non-negative integer that fits into 32 bits, and we need to convert it into a variable-length byte encoding. Instead of always using four bytes, we represent the number using chunks of 7 bits packed into bytes. Each byte stores 7 bits of the number in its lower part, while the highest bit of the byte acts as a continuation flag. If that flag is set, it means more bytes follow; if it is zero, that byte is the last one.
The bytes are produced from the most significant side first, so the first byte contains the highest-order 7-bit chunk of the number, and the last byte contains the lowest-order chunk. This is essentially a base-128 representation written in big-endian order with a continuation bit.
A key constraint is that we are not allowed to introduce leading zero chunks. In practice, this means we cannot start the encoding with a byte whose lower 7 bits are all zero unless the number itself is zero.
The input size is small in the sense that the number is at most $2^{31} - 1$. That means the binary representation has at most 31 bits, so splitting into 7-bit blocks yields at most 5 bytes. Any solution that operates in constant time per test is trivial, and even repeated bit manipulations are negligible.
The main edge case appears when the number is zero. A naive chunking approach may produce an empty sequence or a single zero byte, and it is easy to accidentally violate the “no leading zero byte” rule in how continuation bits are assigned.
Another subtle issue is ordering. If one extracts 7-bit chunks from least significant side first and prints immediately, the result will be reversed unless explicitly corrected. For example, encoding 112025 requires grouping from the most significant bits; reversing the order produces a syntactically valid sequence of bytes but an incorrect decoded value.
Approaches
A direct way to think about the problem is to simulate repeated extraction of 7-bit chunks from the number. At each step, we could take the lowest 7 bits, shift the number right by 7, and store these chunks in a list. This produces a natural decomposition into base 128 digits.
This brute-force idea is correct in terms of representing the number, but it produces digits in reverse order, from least significant to most significant. We would then need to reverse the list before constructing bytes. The cost of this approach is constant-time in theory, since we perform at most five iterations, but the conceptual issue is not performance, it is correctness of ordering and flag placement.
The key observation is that the encoding is exactly base-128 representation, but written with a continuation bit. Once we compute all base-128 digits, we simply need to output them from most significant to least significant. Every digit except the last printed byte must have its continuation bit set.
So the task reduces to converting the integer into base 128, storing digits, then emitting them in reverse with appropriate bit manipulation. This structure makes the problem deterministic and eliminates any need for complex bitwise streaming.
| Approach | Time Complexity | Space Complexity | Verdict |
|---|---|---|---|
| Brute Force (manual streaming without reversal discipline) | O(1) | O(1) | Risky / error-prone |
| Optimal (base-128 decomposition + reversal) | O(1) | O(1) | Accepted |
Algorithm Walkthrough
We process the number by repeatedly extracting 7-bit blocks until the number becomes zero.
- Repeatedly take the lowest 7 bits of the number using a mask, and store them as a chunk. Then shift the number right by 7 bits. This builds the number in base 128 from least significant to most significant.
- Store all chunks in a list. Each chunk represents the payload of one byte before we set continuation flags.
- If the list is empty, it means the number was zero, so we explicitly set a single chunk value 0.
- Reverse the list so that we now process the most significant chunk first.
- For each chunk, convert it into a byte. For all chunks except the last one, set the highest bit to 1 by adding 128. For the last chunk, leave it unchanged.
The reason for reversing is that continuation depends on whether more significant chunks exist. That dependency is only known after collecting all chunks, not during extraction.
Why it works
The algorithm constructs the base-128 representation of the number. Each extracted chunk corresponds exactly to one digit in base 128. Since any integer has a unique base-128 representation, the decomposition is unique. The continuation bit does not alter the numeric value of the chunk, it only encodes structure. Therefore, assigning continuation bits after full decomposition preserves correctness, and reversing ensures the most significant chunk is emitted first, matching the required big-endian encoding.
Python Solution
import sys
input = sys.stdin.readline
n = int(input().strip())
chunks = []
if n == 0:
chunks = [0]
else:
while n > 0:
chunks.append(n & 127)
n >>= 7
chunks.reverse()
res = []
for i in range(len(chunks)):
if i != len(chunks) - 1:
res.append(chunks[i] | 128)
else:
res.append(chunks[i])
print(*res)
The code follows the decomposition strategy directly. The loop n & 127 extracts the lowest 7 bits, which corresponds to a base-128 digit. Shifting by 7 moves to the next digit. The explicit check for zero ensures we output a single byte instead of an empty sequence.
The reversal step is essential because continuation bits depend on the position in the final ordering, not the extraction order. The bitwise OR with 128 sets the continuation flag for all but the last byte.
Worked Examples
Example 1: n = 112025
We first decompose into 7-bit chunks.
| Step | n before | Extracted chunk (n & 127) | n after shift |
|---|---|---|---|
| 1 | 112025 | 25 | 1750 |
| 2 | 1750 | 110 | 13 |
| 3 | 13 | 13 | 0 |
Reversing gives chunks: [13, 110, 25].
We now assign continuation bits.
| Chunk | Position | Output byte |
|---|---|---|
| 13 | first | 13 + 128 = 141 |
| 110 | middle | 110 + 128 = 238 |
| 25 | last | 25 |
Final output: 141 238 25.
This trace shows how decomposition aligns with base-128 digits and how reversal is required before applying continuation flags.
Example 2: n = 0
| Step | Action |
|---|---|
| - | n is zero, no extraction loop runs |
| - | chunks = [0] |
Output is a single byte 0.
This confirms that zero is treated as a special minimal representation and avoids producing an empty encoding.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(1) | At most 5 iterations since 32 bits / 7 bits per chunk |
| Space | O(1) | Only a small fixed list of up to 5 bytes |
The constraints ensure that even the worst-case input requires constant work. The solution is comfortably within both time and memory limits.
Test Cases
import sys, io
def run(inp: str) -> str:
sys.stdin = io.StringIO(inp)
import sys
input = sys.stdin.readline
n = int(input().strip())
chunks = []
if n == 0:
chunks = [0]
else:
while n > 0:
chunks.append(n & 127)
n >>= 7
chunks.reverse()
res = []
for i in range(len(chunks)):
if i != len(chunks) - 1:
res.append(chunks[i] | 128)
else:
res.append(chunks[i])
return " ".join(map(str, res))
# provided samples
assert run("0\n") == "0"
assert run("112025\n") == "141 238 25"
# custom cases
assert run("1\n") == "1", "single small number"
assert run("127\n") == "127", "single byte max without continuation"
assert run("128\n") == "129 0", "boundary crossing 7-bit limit"
assert run("255\n") == "129 127", "two-byte encoding edge"
| Test input | Expected output | What it validates |
|---|---|---|
| 0 | 0 | minimal representation edge case |
| 1 | 1 | smallest non-zero value |
| 127 | 127 | no continuation boundary |
| 128 | 129 0 | first multi-byte boundary |
Edge Cases
Case: n = 0
Input is zero, so no 7-bit decomposition loop runs. The algorithm explicitly assigns a single chunk [0], producing byte 0. There is no continuation bit set, which matches the requirement that encoding must not introduce leading zero chunks.
Case: n = 127
Binary fits exactly in 7 bits. Extraction produces a single chunk [127]. Since there is only one chunk, no continuation bit is added. Output is 127, which correctly represents the value without extra bytes.
Case: n = 128
Binary requires two 7-bit chunks. Extraction gives [0, 1], reversed to [1, 0]. The first byte becomes 1 | 128 = 129, and the second is 0. This matches the expected base-128 split across byte boundary and demonstrates correct handling of leading zero payload in higher chunk.
Case: n = 255
Extraction yields chunks [127, 1], reversed to [1, 127]. The first becomes 1 | 128 = 129, second remains 127. The algorithm correctly encodes values requiring multiple bytes without misplacing continuation bits, showing that ordering is handled independently of bit content.