0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-01-05

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

kmjpさんの回答

題意

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

  • 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
元の題意を考えて、各紙の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と同じか低いなら、答えは存在しません。

    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回目。これは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()

考え方2: SparseTable

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

image.png

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

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()

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?