Official

D - Divide Interval Editorial by toam


良い数列は,セグメント木の区間に対応しています.例えば入力例 \(1\)\(S(3,19)\) を良い数列に分割する方法は,下の図の赤い区間で表現することができます.

左から順番に分割する方法を決めていくことにします.個数が最小になるようにするとき,できるだけ長い区間を貪欲に取っていくことが最善です.

入力例 \(1\ S(3,19)\) を例にとってみます.

  1. 現在の残っている数列は \(S(3,19)\) である.\(3\) を左端とする良い数列は \(S(3,4)\) のみなので \(S(3,4)\) を選択する.残りの数列は \(S(4,19)\) である.
  2. 現在の残っている数列は \(S(4,19)\) である.\(4\) を左端とする良い数列は \(S(4,5),S(4,6),S(4,8)\) なので最も長いの \(S(4,8)\) を選択する.残りの数列は \(S(8,19)\) である.
  3. 現在残っている数列は \(S(8,19)\) である.\(8\) を左端とする良い数列は \((8,9),(8,10),S(8,12),S(8,16)\) なので最も長い \((8,16)\) を選択する.残りの数列は \(S(16,19)\) である.
  4. 現在残っている数列は \(S(16,19)\) である.\(16\) を左端とする良い数列は \(S(16,17),S(16,18),S(16,20)\) である.\(S(16,20)\) は残っている数列を超えているので,それ以外のうち最も長い \(S(16,18)\) を選択する.残りの数列は \(S(18,19)\) である.
  5. 現在残っている数列は \(S(18,19)\) である.\(18\) を左端とする良い数列は \(S(18,19),S(18,20)\) である.\(S(18,20)\) は残っている数列を超えているので,それ以外のうち最も長い \(S(18,19)\) を選択する.残りの数列はない.

左端が \(l\) のとき,あり得る数列の長さは \(2\) べき(非負整数 \(i\) を用いて \(2^i\) と表わされる整数)のうち \(l\) を割り切るものです.よって,左端が \(l\) のときに選択するべき数列は

  • \(l\equiv 0\pmod {2^i}\)
  • \(l+2^i\leq R\) (残っている区間を超えないための条件)

を同時に満たすような最大の \(i\)\(i_0\) としたとき \(S(l,l+2^{i_0})\) になります.

実装例 (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)

また,よい数列に分割する方法をセグメント木の区間に対応させるとき,区間は山型(右上がり→右下がり)になるという性質があります(すなわち,良い数列を \(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))\) としたとき,ある整数 \(k\) が存在して \(i_1<\dots<i_k>\ldots>i_M\) になっています).これを利用して解くこともできます.

実装例 (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)

さらに別の方法として,下から決めていくこともできます.多くのセグメント木の実装では区間クエリを下側から順番に見ていきながら処理していますが,それと同じことをすればよいです.下から順番に見ていって,左側と右側それぞれで長さ \(2^i\) の数列を選択するかを決めていきます.左側で長さ \(2^i\) の数列を選択する条件は

  • \(L\)\(i\) bit 目が立っている.
  • \(L<R\)

です.右側も同様です.

実装例 (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: