🔙
🔝

【paiza問題集 解説】
paizaの森練習問題コンテスト過去問題セット18

値の整列

値の整列

    nums = [int(input()) for _ in range(4)]
    print(*sorted(nums), sep='\n')
    
シープラスプラスプラス

シープラスプラスプラス

    n = int(input())
    print('C' + '+' * n)
    

    文字列もかけ算が先です。😄

大晦日

大晦日

    y, m, d = input().split('-')
    
    if m == '12' and d == '31':
        print('Yes')
    else:
        print('No')
    
背の順に並んでいるか

背の順に並んでいるか

    n = int(input())
    a = [int(input()) for _ in range(n)]
    
    if a == sorted(a):
        print('Yes')
    else:
        print('No')
    

    入力をリストで受け取り、そのリストが昇順ソートしたリストと同じなら背の順に並んでいると言えます。

遠足のお菓子

遠足のお菓子

    N, X = map(int, input().split())
    x = sum(int(input()) for _ in range(N))
    
    if x <= X:
        print('Yes')
    else:
        print('No')
    

    入力 x を全部足して X と比較するだけです。

陣取りゲーム

陣取りゲーム

    class Game:
        def __init__(self, name, h, w):
            self.name = name
            self.adjacents = {(h, w)}
            self.cnt = 1
    
        def bfs(self):
            new_locates = set()
            while self.adjacents:
                y, x = self.adjacents.pop()
                for ny, nx in self.NSWE:
                    yy = y + ny
                    xx = x + nx
                    if (0 <= yy < self.H and 0 <= xx < self.W) and self.S[yy][xx] == '.':
                        new_locates.add((yy, xx))
                        self.cnt += 1
                        self.S[yy][xx] = self.name
    
            self.adjacents = new_locates
    
        @classmethod
        def initialize(cls, H, W, S, turn, players):
            cls.H = H
            cls.W = W
            cls.S = S
            cls.turn = turn
            cls.players = players
            
            cls.NSWE = ((-1, 0), (1, 0), (0, -1), (0, 1))
        
        @classmethod
        def play(cls):
            while any(p.adjacents for p in cls.players):
                now_player = cls.players[cls.turn]
                if now_player.adjacents:
                    now_player.bfs()
                cls.turn = not cls.turn
            
        @classmethod
        def output_result(cls):
            cnt_a = cls.players[0].cnt
            cnt_b = cls.players[1].cnt
            
            print(cnt_a, cnt_b)
            print('A' if  cnt_a > cnt_b else 'B')
    
    
    def setup():
        H, W = map(int, input().split())
        N = input()
        S = [list(input()) for _ in range(H)]
        names = {'A': 0, 'B': 1}
    
        turn = names[N]
        players = [None, None]
        for h in range(H):
            for w in range(W):
                char = S[h][w]
                if char == 'A' or char == 'B':
                    players[names[char]] = Game(char, h, w)
    
        Game.initialize(H, W, S, turn, players)
        
        return
        
    
    def main():
        Game.play()
        Game.output_result()
    
        
    if __name__ == '__main__':
        setup()
        main()
    

    AとBの各プレイヤーが持つ情報が多いので、クラス型で作りました。クラス型は解説もラク😊
    見るからに幅優先探索(BFS)で作ってくださいと言わんばかりの問題です。

    長くて見るのがイヤになるかもしれませんが、重要な所は.bfs() メソッドと、クラスメソッドの .play() メソッドの2つ、稼動部はこの2つだけなんです。

    それでは順を追って解説します。

    【 入力とインスタンスの作成 】

    def setup():
        H, W = map(int, input().split())
        N = input()
        S = [list(input()) for _ in range(H)]
        names = {'A': 0, 'B': 1}
    
        turn = names[N]
        players = [None, None]
        for h in range(H):
            for w in range(W):
                char = S[h][w]
                if char == 'A' or char == 'B':
                    players[names[char]] = Game(char, h, w)
    
        Game.initialize(H, W, S, turn, players)
        
        return
    

    ここでは入力と、A と B の座標取得を行なっています。A と B が交互に陣取りを進めていきますので、A に 0、B に 1 を割り当てます。これはここで作られるインスタンスを players に格納するのですが、この要素番号に使うのと、 .play() メソッド内で A と B のターンを切り換える為の FalseTrue の役目にもなっています。False が 0 でプレイヤーA、True は 1 でプレイヤーBです。

    turn には 入力 N で与えられた先行プレイヤーが格納されているリスト players の要素番号が代入されます。

    の二重ループで A と B の座標を取得します。それぞれの座標を Game クラスの引数に与えてインスタンスを作成します。

    Game.initialize() で、クラス内で使用する為の値をまとめて送ります。

    【 クラスの準備 】

    @classmethod
    def initialize(cls, H, W, S, turn, players):
        cls.H = H
        cls.W = W
        cls.S = S
        cls.turn = turn
        cls.players = players
        
        cls.NSWE = ((-1, 0), (1, 0), (0, -1), (0, 1))
    

    setup() 関数から送られてきたクラス内で使用する値を、各クラス変数に格納します。インスタンスもここに格納してクラス内で使えるようにしておきます。

    【 コンストラクタ 】

    def __init__(self, name, h, w):
        self.name = name
        self.adjacents = {(h, w)}
        self.cnt = 1
    

    self.name は、マップ内の占領地に印を付けるために使う文字です。A か B が入ります。

    self.adjacents は、最も外側にある領地の座標が格納されます。この座標を使って周辺に占領できる空白地があるかどうかを調べます。

    self.cnt は、占領したマスの数です。最初は必ず1つから始まりますので 1 を代入します。


    準備は以上です。次からいよいよゲームが始まります。

    .play() メソッド 】

    @classmethod
    def play(cls):
        while any(p.adjacents for p in cls.players):
            now_player = cls.players[cls.turn]
            if now_player.adjacents:
                now_player.bfs()
            cls.turn = not cls.turn
    

    main() 関数からこのメソッドが呼び出されます。ここがメインとなるプログラム部分です。ここでは A もしくは B に、次に探索する座標が残っている場合にループします。その後、.bfs() を呼び出し、領地拡大を図ります。

    now_player は、インスタンス cls.players[cls.turn] に名前を付けて短縮化するために行なっています。値のコピーではなく、アドレス参照です。

    ターンを終えたら、次の人とターンを交代します。

    .bfs() メソッド 】

    def bfs(self):
        new_locates = set()
        while self.adjacents:  # 1番
            y, x = self.adjacents.pop()
            for ny, nx in self.NSWE:  # 2番
                yy = y + ny
                xx = x + nx
                # 3番
                if (0 <= yy < self.H and 0 <= xx < self.W) and self.S[yy][xx] == '.':
                    new_locates.add((yy, xx))  # 4番
                    self.cnt += 1  # 5番
                    self.S[yy][xx] = self.name  # 6番
    
        self.adjacents = new_locates  # 7番
    
    1. ループは、今回探索する座標が入っている self.adjacents が空になるまでです。ここから1つポップして、(y, x) に座標を入れます。
    2. while self.adjacents:
          y, x = self.adjacents.pop()
      
    3. そして次の ループで NSWE (方角) の順に上下左右を探索します。
    4. for ny, nx in self.NSWE:
          yy = y + ny
          xx = x + nx
      
    5. チェックする座標がマップの範囲内、もしくは '.' ならば、占領します。'#' という障害物があってもこの条件文の書き方ならお構いなしです。
    6. if (0 <= yy < self.H and 0 <= xx < self.W) and self.S[yy][xx] == '.':
      
    7. new_locates は、この探索中に新しく占領した座標を格納するセットです。 self.adjacents に入れてしまうと、今回探索分と次回探索分が混ざってしまい、マップ全体を探索し尽くしてしまいますので、一時的に別のセットを作ってそこに入れておきます。
      self.adjacents は今回探索する座標リスト、 new_locates は次回探索分の座標リストです。
    8. new_locates = set()
      
      new_locates.add((yy, xx))
      
    9. 1つ占領したので、1つカウントします。
    10. self.cnt += 1
      
    11. 占領箇所を自分の名前 A または B に書き換えます。フラグを立てるような感じです。
    12. self.S[yy][xx] = self.name
      
    13. 今回分の探索を終えると self.adjacents は空になり、 new_locates には次回探索分の座標リストが入っています。空になった self.adjacentsnew_locates を入れて、次回ターンはこれらの座標を探索します。
    14. self.adjacents = new_locates
      

    ターンが終了したら .play() メソッドに戻り、次のターンの人に切り換わります。

    【 ゲーム終了 】

    @classmethod
    def play(cls):
        while any(p.adjacents for p in cls.players):
            if cls.players[cls.turn].adjacents:
                cls.players[cls.turn].bfs()
            cls.turn = not cls.turn
    

    A と B の adjacents が共に空になったら全ての探索が終了したことになります。


    def main():
        Game.play()
        Game.output_result()
    

    最後に結果を画面に出力します。


    @classmethod
    def output_result(cls):
        cnt_a = cls.players[0].cnt
        cnt_b = cls.players[1].cnt
        
        print(cnt_a, cnt_b)
        print('A' if  cnt_a > cnt_b else 'B')
    

    引き分けは無いということなので、引き分けの判定無しで簡単に出力しています。