典型90【012 – Red Painting(★4)】をpython解説

Python

AtCoder の問題を解くために必要な実力を付けるために作られた「典型問題」を解いていく企画として、「典型90」があります。

今回は、その内、の12問目である Red Painting(★4)を解説していきたいと思います。
なにか、間違っているところなどありましたら、@EitaSugiまで、ご指導ご鞭撻の程、よろしくお願い致します!

問題名 【012 – Red Painting(★4)】

012 – Red Painting(★4)

考察

まず、今回与えられるクエリから、どのような操作をする必要があるかを考えます。
(問題文の読み替えするということです)

query1
(r,c)のマスを赤くする 
→(r,c)に隣接する赤マスがあればそのマスと連結する
query2
(ra,ca)から(rb,cb)へ到達可能か?
→これまでのquery1の操作を通して、(ra,ca)と(rb,cb)は連結されているか?

今回、上記の赤字部分から、

  • 2点間が連結可能か(query1)
  • 連結されているかを判別する(query2)

が必要があることがわかりました。

そのため、連結判定を高速で行えるunionfindを用いて実装していきます。
unionfindとは、グラフ系の問題でよく出てくる典型解法で、

  • 2点間を繋いだり、
  • 連結されているかを判別する

典型解法なので、今回の問題にぴったりです。

操作

前処理
まず、H*Wの二次元のマス目なので、それをグラフで扱いやすくするため1配列にしていきます。
i = r * W + c とすることで、二次元配列を1つのノードに変換します。
また、(r,c)が赤マスかを判別するためにH*Wのflgという2次元配列を用意しておきます。

if query1
flg[r][c] = True と、そのマスが赤マスの印を立てて置きます。
その後、隣接する4マスについて、H*Wマスの範囲内でかつ、赤マスなら連結させます。

# 探索するノード
i1 = (r-1)*W +c
i2 = (r+1)*W +c
i3 = r * W +c+1
i4 = r* W + c-1

if query2
(ra,ca),(rb,cb)が両方とも赤マスかつ連結されているのなら
Yesを返します。

典型解法:連結判定はUnionFind

コード

import sys
def LI(): return list(map(int,sys.stdin.readline().rstrip().split()))
def II(): return int(sys.stdin.readline())
def MI(): return map(int,sys.stdin.readline().rstrip().split())
def S(): return sys.stdin.readline().rstrip()
readline = sys.stdin.readline
sys.setrecursionlimit(10 ** 6)

H,W = MI()
Q = II()
from typing import List

class UnionFind:
    """0-indexed"""

    def __init__(self, n):
        self.n = n
        self.parent = [-1] * n
        self.__group_count = n

    def unite(self, x, y) -> bool:
        """xとyをマージ"""
        x = self.root(x)
        y = self.root(y)

        if x == y:
            return False

        self.__group_count -= 1

        if self.parent[x] > self.parent[y]:
            x, y = y, x

        self.parent[x] += self.parent[y]
        self.parent[y] = x

        return True

    def is_same(self, x, y) -> bool:
        """xとyが同じ連結成分か判定"""
        return self.root(x) == self.root(y)

    def root(self, x) -> int:
        """xの根を取得"""
        if self.parent[x] < 0:
            return x
        else:
            self.parent[x] = self.root(self.parent[x])
            return self.parent[x]

    def size(self, x) -> int:
        """xが属する連結成分のサイズを取得"""
        return -self.parent[self.root(x)]

    def all_sizes(self) -> List[int]:
        """全連結成分のサイズのリストを取得 O(N)
        """
        sizes = []
        for i in range(self.n):
            size = self.parent[i]
            if size < 0:
                sizes.append(-size)
        return sizes

    def groups(self) -> List[List[int]]:
        """全連結成分の内容のリストを取得 O(N・α(N))"""
        groups = dict()
        for i in range(self.n):
            p = self.root(i)
            if not groups.get(p):
                groups[p] = []
            groups[p].append(i)
        return list(groups.values())

    @property
    def group_count(self) -> int:
        """連結成分の数を取得 O(1)"""
        return self.__group_count

uf = UnionFind(H*W)#H*Wのnodeを用意
box = [[0,1],[0,-1],[-1,0],[1,0]]#隣接するマス
flg = [[False]*W for i in range(H)]#そのマスが赤マスかどうかを判断
for q in range(Q):
    query= LI()
    if len(query)==3:#query1なら
        r,c = query[1],query[2]
        r -= 1#0始まりのindexに直すため-1
        c -= 1
        flg[r][c] = True# そのマスを赤マスにする
        for x_y in box:#隣接するマスの探索
            y = x_y[0]+r
            x = x_y[1]+c
            if -1 < y < H and -1 < x < W:#隣接するマスがH*Wの範囲内で
                if flg[y][x]== True:#かつ赤マスなら
                    uf.unite(y*W+x,r*W+c)#連結
    
    else:#query2なら
        ra,ca,rb,cb = query[1],query[2],query[3],query[4]
        ra -= 1#0始まりのindexに直すため-1
        ca -= 1
        rb -= 1
        cb -= 1
        if flg[ra][ca] == True and flg[rb][cb] == True:#2点が赤マスで
            if ra == rb and ca == cb:#そもそも2点が同じ点なら
                print("Yes")
                continue
            if uf.is_same(ra*W+ca,rb*W+cb):#2点が連結可能なら
                print("Yes")
            else:
                print("No")
        else:
            print("No")

まとめ

UnionFindのアルゴリズムを最初に考えた人天才すぎん?

前の問題【010 – Score Sum Queries(★2)】
次の問題【013 – Passing(★5)】

コメント

タイトルとURLをコピーしました