Codeforces Round #693 (Div. 3) E. Correct Placement: 左下にある点の検出 (と Sparse Tableを使った全数からの検索)

https://codeforces.com/contest/1472/problem/E

コンテスト中に解けなくて、SparseTableを使ってupsolveした！ら、kmjpさんのsubmitをみて確かにそれはそうだ...となった解。 (それまでにあり得るminをとるということで)アプローチは同じなのだけど、<b><font color=red>データ構造で殴って思考停止になるのはダメ・ゼッタイ</font></b>と思いました。

[kmjpさんの回答](https://codeforces.com/contest/1472/submission/103206707)

# 題意
問題では写真撮影とされていますが、同じ意味を長方形で示します。

- n個 ($1 \leq n \leq 10^5$)の長方形(正方形かもしれない)があります。縦横を$h,w$($1 \leq n \leq 10^9$)とします。それぞれ、$a_1, a_2 ... a_n$として区別します。
- 各長方形について、それよりも完全に小さい長方形が存在すればそのindexを述べ、存在しなければ-1を出力してください。(つまり、縦か横が"同じではだめ"です。)
- 各長方形は縦横を入れ替えてもよいです。
- 複数の候補がある場合、どれを答えてもよいです。

## 例

- 「サンプル1」1."3x4", 2"5x4, 3."3x3"があった時、2だけは3よりも大きいです。1はwだけ3よりも大きいですが、hが同じため要件を満たしません。3は1,2を隠すことはできません。[-1, 3, -1]を出力します。
- 「サンプル」1. 3x6, 2. 4x2があった時、2はひっくり返すと、2x4として扱えます。このため、1にとって2は小さい長方形です。[2, 1]を出力します。
- 「サンプル」1. 3x3, 2. 3x3があった時、同じ長方形は小さいと呼べないので[-1, -1]です。

# 問題の言い換え

- n個の点があります。それぞれの座標は$x, y$ (ただし、$1 \leq x \le y \leq 10^9$)です。それぞれの点を$a_1, a_2 ... a_n$とします。
- 各点について、その点よりも左下の座標に点があればそのindexを答えてください。そのような点がない場合は-1を答えてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30938/6be2ab57-e461-b56b-13d2-219ce2978158.png)
 元の題意を考えて、各紙の1点を原点に合わせて$x,y$軸に沿って第一象限に置くとすると、$h,w$を$x,y$として扱ってよいです。それぞれの紙は回転させて良いので、$x \leq y$となるように置いて考えます。(図中青字の「回転」)この時、完全に小さい紙とは、より左下にある点(の紙)であることは明らかです。

# 考え方1: xを小さい方から見ていく
今、$x$が小さい方からすべての点を見ていくとします。ある程度の点を見た後に、点$a,b$より小さい紙(左下の点)$c,d$を見つけたいとします。

- この時、$c$は$a$よりも小さい点であることが必要です。これは、$x$を小さい方から見ているため、すべて走査済みです。ただし、いままで見てきたすべての点には、$x=a$の点も含まれるので、それらを含めてはいけません。(ちょうど$x$の幅は重なってしまうので答えになりません)
- $c < a$であるならばその中で高さがbよりも低い点はすべて解になり得ます。つまり、これまでの高さの$min$をとっていればそれが存在するかを確認できます。

上の図で今、紫の点より左下の点を探したいとします。まず、xがより小さい点(頭上はほかのすべての点)が候補になり得ます。このxの点たちを確認するまでのmin(図では赤)が紫の点より低いなら、それは答えです。
もしも、紫の点がこれまでに見てきた点のminと同じか低いなら、答えは存在しません。

```Python
    import sys
    input = sys.stdin.readline
    def do():
        import collections
        n = int(input())
        m = collections.defaultdict(list)
        data = []
        datb = []
        for i in range(n):
            a, b = map(int, input().split())
            a, b = min(a,b), max(a,b) #x軸方向を小さい数字にする
            data.append(a)
            datb.append(b)
            m[a].append(i) # 各xの値毎にindex(点)をリストで保存する ための辞書
        mi = 2 ** 60 # 初期値。最初のx in klのループでは答えは存在しない
        ret = -1
        r = [0] * n # 結果
        kl = list(m.keys()) # xを小さい方から見たいので、辞書のキーを小さい方から見ていく
        kl.sort()
        # このループでxが小さい座標から見ていく
        for x in kl:
            # ループ1回目。これは１つ前のxのループまでのminの比較
            for ind in m[x]: # indはそのx座標にいる点のindex
                if mi < datb[ind]: # もし、その点のy座標がこれまでのxのmin(y)より高いなら
                    r[ind] = ret # 答えは存在する
                else:
                    r[ind] = -1 # そうでないなら答えは存在しえない
            # ループ2回目。これは「そのxを含めて」最小のmin(y)を計算する
            for ind in m[x]:
                if datb[ind] < mi:
                    mi = datb[ind]
                    ret = ind + 1 # そのindexを答えように保存
        print(" ".join(list(map(str, r))))
    q = int(input())
    for _ in range(q):
        do()
```
https://codeforces.com/contest/1472/submission/103367278

# 考え方2: SparseTable
この問題を愚直に考えると<br>$h$ x $w$の紙があった時、$h-1$までのすべての紙の中で最小の幅が$w-1$ならそれが答えです。</br>つまり、minの値とindexを一緒に返せるRMQ(min)が行えればよいです。(h,wをひっくり返して試行します)。ただし、$1 \leq n \leq 10^9$なので、座標圧縮が必要です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30938/af7f2828-c0a9-77b3-58f4-939fbc721a09.png)

わざわざ考え方2と書いておいてなんですが、「一辺よりも小さい点の中で、もう一辺がより小さいminと比べる」というアプローチは考え方1と同じです。<b>考え方1では、各エントリ(=xごとの点)を順番に見ていき、1つ小さいxまでのminを保持しながら解を求めており賢いです。</b>

```Python
class sparseTable(object):
    func = min

    def __init__(self):
        self.table = []
        self.depthTreeList = 0

    def load(self, l):
        self.n = len(l)
        self.depthTreeList = (self.n - 1).bit_length()  # Level
        self.table.append(l)
        for curLevel in range(1, self.depthTreeList):
            l = []
            for i in range(self.n - (2 ** curLevel - 1)):
                n1 = self.table[curLevel - 1][i]
                n2 = self.table[curLevel - 1][i + (2 ** (curLevel - 1))]
                if n1[0] < n2[0]:
                    l.append(n1)
                else:
                    l.append(n2)
            self.table.append(l)

    def query(self, l, r):  # [l, r)
        diff = r - l
        if diff <= 0:
            raise
        if diff == 1:
            return self.table[0][l]
        level = (diff - 1).bit_length() - 1
        n1 = self.table[level][l]
        n2 = self.table[level][r - (2 ** level)]
        if n1[0] < n2[0]:
            return n1
        else:
            return n2

def do():
    import sys
    input = sys.stdin.readline

    from bisect import bisect_left, bisect_right
    n = int(input())
    dat = []
    INF = 2**60
    zatsu = set()
    for i in range(n):
        num = i + 1
        a, b = map(int, input().split())
        a,b = min(a,b), max(a,b)
        zatsu.add(a)
        zatsu.add(b)
        dat.append([a, b, num])
    if n == 1:
        print("-1")
        return

    # 座標圧縮
    zatsu = list(zatsu)
    zatsu.sort()
    zatsu = list(enumerate(zatsu))
    zTable = dict()
    for i in range(len(zatsu)):
        zTable[zatsu[i][1]] = zatsu[i][0]
    sList = [[INF, -1] for i in range( len(zatsu))]
    # 座標圧縮に点を対応させるとともに、Sparse Tableに乗せるテーブルを作る
    for i in range(n):
        a,b,num = dat[i]
        a,b = zTable[a], zTable[b]
        dat[i][0] = a
        dat[i][1] = b
        if sList[a][0] > b:
            sList[a] = [b, num]
        if sList[b][0] > a:
            sList[b] = [a, num]

    st = sparseTable()
    st.load(sList)
    res = []

    # w, hを総当たりで、-1までの区間にもう一方より小さい点があるかを確認
    # あるなら、そのindexを答えとする
    for i in range(n):
        a,b,num = dat[i]
        xres = -1
        if a > 0:
            x = st.query(0, a)
            if x[0] != INF and x[0] < b:
                xres = x[1]
        if b > 0:
            x = st.query(0, b)
            if x[0] != INF and x[0] < a:
                xres = x[1]
        res.append(xres)
    print(" ".join(list(map(str, res))))

q = int(input())
for _ in range(q):
    do()
```
https://codeforces.com/contest/1472/submission/103339409