典型90

典型90 012 – Red Painting(★4)

はじめに

競プロの精進のために学びがあった問題についてまとめています。 目標は AtCoder 水色!

今のレートは茶色です。

問題

典型90 012 – Red Painting(★4)を解きました。

解説スライドはこちら

考察

条件式のうち、「マス (rai,cai)(rai,cai) からマス (rbi,cbi)(rbi,cbi) まで赤色マス上を上下左右に移動することで辿り着ける」については、幅優先探索で実装することは可能ですが、計算量はO(N^2)になるため、今回のH、Wの最大値はそれぞれ2000なので、最悪計算量の想定の場合は耐えられません。

そこで、「マス (rai,cai)(rai,cai) からマス (rbi,cbi)(rbi,cbi) まで赤色マス上を上下左右に移動することで辿り着ける」という判定を高速化できる方法について考えます。

このような、「AからBにたどり着けるか」という問いはすなわち、「AとBが同じグループに存在するか」という判定問題に置き換えて、Union-Find木で効率的に探索することが可能です。

Union-Findについて

Union-findは、グループ分けを管理するためのデータ構造で、以下の2つの処理を高速に処理することができます。

  1. ある要素とある要素が同一のグループに存在するかを調べる
  2. ある要素とある要素を含むグループを、1つのグループに併合する。

そこで今回は、上下左右の移動でたどりつける連続した赤マスを1つのグループとしてとらえて、Union-find木を構築していきます。

Union-findを実装する場合にポイントになるのが、グループを根付き木で表現する点です。

ある数が同じグループに存在しているかについては、グループの根が一致するかで判断します。 また、同じグループに存在する場合は、すべての子要素を直接根に繋ぎかえます。

子要素を繋ぎかえる際は、子要素数が小さい木の各要素を、子要素が大きい木に繋ぎかえる方が計算量が小さくてすみます。

というわけで、Union-findについてPythonで実装してみます。

class Union_find:

    def __init__(self, N):
        self.par = [-1] * (N+1)
        self.siz = [1] * (N+1)


    def root(self, x):
        if self.par[x] == -1:
            # root
            return x

        else:
            self.par[x] = self.root(self.par[x])
            return self.par[x]
        
    
    def issame(self, x, y):
        return self.root(x) == self.root(y)


    def unite(self, x, y):
        x = self.root(x)
        y = self.root(y)

        if x == y:
            return False
        
        else:
            if self.siz[x] < self.siz[y]:
                x, y = y, x
            
            self.par[y] = x;
            self.siz[x] += self.siz[y]

            return True


    def get_size(self, x):
        return self.siz[self.root(x)]


def main():
    N = 7
    uni = Union_find(100)
    uni.unite(1,2)
    uni.unite(2, 3)
    uni.unite(5, 6)

    print(uni.issame(1, 3))
    print(uni.issame(2, 5))

    uni.unite(1, 6)
    print(uni.issame(2, 5))


if __name__ == "__main__":
    main()

これをつかって、今回の問題についても解いてみました。

コード

class Union_find:
    
    def __init__(self, N):
        self.par = [-1] * (N+1)
        self.siz = [1] * (N+1)


    def root(self, x):
        if self.par[x] == -1:
            # root
            return x

        else:
            self.par[x] = self.root(self.par[x])
            return self.par[x]
        
    
    def issame(self, x, y):
        return self.root(x) == self.root(y)


    def unite(self, x, y):
        x = self.root(x)
        y = self.root(y)

        if x == y:
            return False
        
        else:
            if self.siz[x] < self.siz[y]:
                x, y = y, x
            
            self.par[y] = x;
            self.siz[x] += self.siz[y]

            return True


    def get_size(self, x):
        return self.siz[self.root(x)]

H, W = map(int,input().split())
Q = int(input())
G = Union_find(H*W+1)
M = [-1] * (H*W+1)

def check(n, x):
    if M[x] == 1 and M[n] == 1:
        G.unite(n, x)
    return

for i in range(Q):
    A = list(map(int,input().split()))
    if A[0] == 1:
        n = W * (A[1] - 1) + (A[2] - 1)
        M[n] = 1

        if n - W >= 0:
            check(n, n - W)
        if n + W < H*W:
            check(n, n + W)
        if n % W != 0 and n - 1 >= 0:
            check(n, n - 1)
        if (n + 1) % W != 0 and n + 1 < H*W:
            check(n, n + 1)

    else:
        n1 = W * (A[1] - 1) + (A[2] - 1)
        n2 = W * (A[3] - 1) + (A[4] - 1)

        if G.issame(n1, n2) and M[n1] == 1 and M[n2] == 1:
            print("Yes")

        else:
            print("No")

まとめ

ペナ出しまくってめっちゃ時間かかりました。。

イージーな実装ミスつらい。

COMMENT

メールアドレスが公開されることはありません。