Official

D - Divide Interval Editorial by en_translator


A good sequence corresponds to a node of a segment tree. For example, the way to divide \(S(3,19)\) in Sample Input \(1\) can be represented by the red nodes illustrated below.

Let us determine the division from left. When it comes to minimizing the number of parts, it is optimal to greedily take as long segment as possible.

Let us take Sample Input \(1\), \(S(3, 19)\) as an example.

  1. Currently, sequence \(S(3,19)\) remains. The only good sequence starting with \(3\) is \(S(3,4)\), so choose \(S(3,4)\). Now \(S(4,19)\) remains.
  2. Currently, sequence \(S(4,19)\) remains. The good sequences starting with \(4\) are \(S(4,5),S(4,6)\), and \(S(4,8)\), so choose the longest one among them, \(S(4,8)\). Now \(S(8,19)\) remains.
  3. Currently, sequence \(S(8,19)\) remains. The good sequences starting with \(8\) are \((8,9),(8,10),S(8,12)\), and \(S(8,16)\), so choose the longest one among them, \((8,16)\). Now \(S(16,19)\) remains.
  4. Currently, sequence \(S(16,19)\) remains. The good sequences starting with \(16\) are \(S(16,17),S(16,18)\), and \(S(16,20)\). \(S(16,20)\) sticks out of the remaining sequence, so choose the longest one among the others, \(S(16,18)\). Now \(S(18,19)\) remains.
  5. Currently, sequence \(S(18,19)\) remains. The good sequences starting with \(16\) are \(S(18,19)\) and \(S(18,20)\). \(S(18,20)\) sticks out of the remaining sequence, so choose the longest one among the others, \(S(18,19)\). No sequence remains.

The good sequences with left end \(l\) are those whose length is a power of two (an integer represented as \(2^i\) for some non-negative integer \(i\)) that divides \(l\). Thus, when the left end is \(l\), the sequence that should be chosen is \(S(l,l+2^{i_0})\), where \(i_0\) is the maximum \(i\) such that

  • \(l\equiv 0\pmod {2^i}\), and
  • \(l+2^i\leq R\) (so that it does not sticks out of the remaining sequence).

Sample code (Python)

L, R = map(int, input().split())
ans = []
while L != R:
    i = 0
    while L % pow(2, i+1) == 0 and L+pow(2, i+1) <= R:
        i += 1
    ans.append([L, L+pow(2, i)])
    L += pow(2, i)
print(len(ans))
for l, r in ans:
    print(l, r)

Alternatively, when a division into good sequences is corresponded to nodes of a segment tree, the segments form a “concave” shape (rising, then descending). (In other words, when the division consists of good sequences \(S(l_1,r_1)=(2^{i_1}j_1,2^{i_1}(j_1+1)),\ldots,S(l_M,r_M)=(2^{i_M}j_M,2^{i_M}(j_M+1))\), then there exists an integer \(k\) such that \(i_1<\dots<i_k>\ldots>i_M\).) One can use this property to solve this problem too.

Sample code (Python)

L, R = map(int, input().split())
ans = []
for i in range(61):
    if L % (pow(2, i+1)) == pow(2, i) and L+pow(2, i) <= R:
        ans.append([L, L+pow(2, i)])
        L += pow(2, i)
for i in range(60, -1, -1):
    if L+pow(2, i) <= R:
        ans.append([L, L+pow(2, i)])
        L += pow(2, i)
print(len(ans))
for l, r in ans:
    print(l, r)

Yet another solution would be determining bottom-up. Just as most implementation of segment trees, one can inspect the segment from bottom to top. From the bottommost nodes, determine if length-\(2^i\) segments must be chosen at both ends. At the left end, the length-\(2^i\) sequence must be chosen if and only if:

  • the \(i\)-th bit of \(L\) is set, and
  • \(L<R\).

Same applies to the right end.

Sample code (Python)

L, R = map(int, input().split())
ans_left, ans_right = [], []
i = 0
while L < R:
    if (L >> i) & 1:
        ans_left.append([L, L+(1 << i)])
        L += 1 << i
    if (R >> i) & 1:
        ans_right.append([R-(1 << i), R])
        R -= 1 << i
    i += 1
ans = ans_left+ans_right[::-1]
print(len(ans))
for l, r in ans:
    print(l, r)

posted:
last update: