🔙
🔝

【paiza問題集 解説】
Bランク・スキルチェック過去問題セット

みんなでしりとり

みんなでしりとり

FINAL問題 みんなでしりとり

みんなでしりとり

    from collections import deque
    
    class Player:
        word_set = None
        num_player = 0
        already_said = set()
        last_letter = ''
    
        def __init__(self):
            self.num = self.join()
            self.s = None  # shiritori() で代入
            
        def shiritori(self):
            self.s = input()
            if any(self.is_rule_violated()):
                self.leave_game()
            else:
                Player.last_letter = self.s[-1]
    
            self.already_said.add(self.s)
    
        def is_rule_violated(self):
            is_already_said = (self.s in self.already_said)
            is_not_in_word_set = (self.s not in Player.word_set)
            is_last_z = (self.s[-1] == 'z')
            is_not_shiritori = self.is_not_shiritori()
            
            return is_already_said, is_not_in_word_set, is_last_z, is_not_shiritori
        
        def is_not_shiritori(self):
            is_not_first_say = Player.last_letter
            is_not_shiritori = (Player.last_letter != self.s[0])
    
            return all((is_not_first_say, is_not_shiritori))
    
        def leave_game(self):
            Player.num_player -= 1
            Player.last_letter = ''
    
        def is_gaming(self):
            return self.last_letter
    
        def join(self):
            Player.num_player += 1
            return Player.num_player
        
        def output_winner(self):
            print(self.num)
    
    
    def output_result(players):
        print(Player.num_player)
        for player in sorted(players, key=lambda x:x.num):
            player.output_winner()
        
        return
    
    def main(M, players):
        for _ in range(M):
            now_player = players.popleft()
            now_player.shiritori()
            
            if now_player.is_gaming():
                players.append(now_player)
    
        output_result(players)
    
    def setup():
        N, K, M = map(int, input().split())
        word_set = set(input() for _ in range(K))
        Player.word_set = word_set
        players = deque([Player() for _ in range(N)])
    
        return M, players
    
    
    if __name__ == '__main__':
        main(*setup())
    

    複雑でなかなかの難易度だと思います。クラス型で組むと長くなってしまいますが、それぞれの関数やメソッドが何をしているのか見てわかると思います。

    クラス変数には「発言で使える単語リスト」「参加中のプレイヤー人数」「発言済み単語リスト」「前者が発言した単語の最後の文字」を持たせています。
    インスタンス変数には「プレイヤー番号」を持たせています。self.s は、発言中のプレイヤーの発言ワードです。

    インスタンスを作成すると、自動でプレイヤー番号が与えられるようになっています。ゲームから脱落すると脱落者のインスタンスを消すので、残っているプレイヤーが何番のプレイヤーかを最後に出力するのに使います。このインスタンスは双方向キューの deque に格納します。訳については後程説明します。

    セットアップが終わったら main() 関数です。

    def main(M, players):
        for _ in range(M):
            now_player = players.popleft()
            now_player.shiritori()
    

    まず players の先頭のプレイヤー(インスタンス)を取り出して、変数 now_player に移します。以降、このプレイヤーのターンとなります。


    def shiritori(self):
        self.s = input()
        if any(self.is_rule_violated()):
            self.leave_game()
        else:
            Player.last_letter = self.s[-1]
    
        self.already_said.add(self.s)
    

    ルールは4+1個です。

    1. 発言は、単語リストの単語のみ可
    2. 直前に出た単語の末尾の文字を頭文字とした単語を選ぶこと
    3. 直前のプレイヤーが脱落したら、ルール2 は不履行
    4. 発言済みの単語は使えない
    5. z で終わる単語は使えない

    if any(self.is_rule_violated()): の内容はこちらです。

    def is_rule_violated(self):
        is_already_said = (self.s in self.already_said)
        is_not_in_word_set = (self.s not in Player.word_set)
        is_last_z = (self.s[-1] == 'z')
        is_not_shiritori = self.is_not_shiritori()
        
        return is_already_said, is_not_in_word_set, is_last_z, is_not_shiritori
    

    ルール違反をしている場合は True と判定しています。

    次のプログラムではルールを1つでも違反している場合は True、違反なく全てパスしている場合は False で処理されます。

    if any(self.is_rule_violated()):
        self.leave_game()
    else:
        Player.last_letter = self.s[-1]
    

    ルール2 と ルール3 の判定は、

    def is_not_shiritori(self):
        is_not_first_say = Player.last_letter
        is_not_shiritori = (Player.last_letter != self.s[0])
    
        return all((is_not_first_say, is_not_shiritori))
    

    このメソッドで行なわれています。最後に発言された単語の末尾の文字は、クラス変数の last_letter に入っています。この文字と今回発言した単語の頭文字が不一致ならば True を返します。(ルール違反をしたということです)
    last_letter の文字列が '' ヌルストリング(null string) の時は ルール3 が適用され、頭文字は考慮しなくてよくなります。

    この判定がややこしくて困りますね。評価をルールに違反していない場合で組むよりも、このプログラム例や paiza の解答コード例のように、評価を「ルールに違反した場合」にしたほうが組みやすい気がします。

    これらルールをオールグリーンでパスしたら Player.last_letter に、今回発言した単語の末尾の文字を代入します。

    そして、発言した単語は発言済みリスト(セットだけど)に追加します。

    ※ ここだけの話、ルール2 の判定をしなくても 100点が取れてしまいます。もはやしりとりではない。😓

    次に、失言で脱落したプレイヤーの行く末を処理します。😱

    def leave_game(self):
        Player.num_player -= 1
        Player.last_letter = ''
    

    Player.num_player から 1 を引いてプレイヤー人数を減らします。
    Player.last_letter'' にするのですが、これは、

    def is_gaming(self):
        return self.last_letter
    

    ここで '' を脱落者とみなしている為です。''False、文字が入っていれば True です。

    この判定については「2章 bool型 True と False」で学習できます。bool型の学習は重要度大です!

    同時に、

    def is_not_shiritori(self):
        is_not_first_say = Player.last_letter  # ← ココ
        is_not_shiritori = (Player.last_letter != self.s[0])
    
        return all((is_not_first_say, is_not_shiritori))
    

    この部分でも ルール3 の履行判定として使っています。'' の時は直前のプレイヤーが脱落したので、頭文字は何でもよいということです。


  • main() 関数
  • if now_player.is_gaming():
        players.append(now_player)
    

    脱落者の処分処理はまだ終わりではありません。
    プレイヤーのインスタンスは players の先頭からポップして now_player に移しています。現在 players にあるインスタンスは待機中のプレイヤーで、now_player は現在のターン中のプレイヤーです。

    オールグリーンでパスした now_player はゲーム続行可能な為、players の末尾(順番の最後)に再び加えるという二行です。
    しかし脱落者は players に追加されず、そのまま消されます。😱コワイ

    こうして players には常にゲームに参加中のプレイヤーのみが残り、順序が回転しながらゲームが継続されていきます。

    def output_result(players):
        print(Player.num_player)  # 出力 1
        for player in sorted(players, key=lambda x:x.num):
            player.output_winner()  # 出力 2
        
        return
    

    最後に結果を画面に出力します。1つは勝ち残っているプレイヤーの人数です。これはクラス変数の num_player を出力するだけです。ここから直接クラス変数にアクセスして画面に出力しています。

    もう1つは勝ち残ったプレイヤーの番号です。昇順で出力することになっていますので、昇順ソートして順に画面に出力していきます。lambda で直接 num をターゲットに指定します。num とは、インスタンス変数の self.num、インスタンス作成時に各プレイヤーに与えられた番号です。
    これだけで num をターゲットにインスタンスを昇順ソートしてくれます。簡単だ。😁

    この問題は「プレイヤー」を複数作成させるということでクラス型で作りました。他にもキューを使っています。Bランク問題は時々あまりに複雑すぎてクラスで作ったほうがわかりやすくなる問題もあります。特に人や物が持つ値が多い時はインスタンスがものすごく便利です。

    オブジェクト指向をまったく知らなくても、まったく使わなくてもプログラムは組めますが、複雑なプログラムほどクラス型は重宝します。ぜひ頑張って勉強してクラス型で書けるようになっておきましょう。

    わかるようになると全然難しくなかったりするんですよね、これが。😅 あまりに構造が違うために最初に難しく考えすぎてしまうから、実は簡単であることに気付けないでいるだけなんです。またイチからの学び直しと感じて気分が憂鬱になってしまうのもあります。手続型で不便はないと感じていても、クラス型で組めるようになると今度はクラス型でないと不便だと感じるようになったりします。

    私がオブジェクト指向を理解したきっかけが、私はとあるメーカーの事務機器の製造や修理に関わる工場勤務をしていたのですが、「あ、アレと同じじゃない?」と気付いたら一気に組めるようになりました。フレーム(本体)にアッセンブリと呼ばれる組み立て済みのユニットを次々嵌めていく様がモジュールと一致します。そのアッセンブリを組み立てる時に使う部品1つ1つがメソッドと一致します。コンストラクタはコネクタで、引数は機能設定で、メソッドの呼び出しは操作ボタンで、スタートボタンを1回押すだけで全てを自動でやってくれるところがオブジェクト指向のしくみや思想と一致します。「カプセル化はカバーじゃね?だったらカプセル化は大事だよ!」とか思ったらなんだか可笑しくなりました。🤣

    スキルチェックBランク問題は手続型でも組めますが、クラス型を学んだらもう一度そのプログラムをクラス型で組みなおしてみるとメキメキと上達していくと思います。私がそうでした。今は ChatGPT があるからとても学びやすい!

    初歩の部分だけですが「3章 オブジェクト指向」でクラス型の基本形を学習できます。
    問題集の「クラス・構造体メニュー」もまだの方はぜひ修了しておきましょう!

    オブジェクト指向は面白いよ!

3Dプリンタ

3Dプリンタ

FINAL問題 3Dプリンタ

3Dプリンタ

    X, Y, Z = map(int, input().split())
    results = [['.'] * Y for _ in range(Z)]
    
    for z in range(Z):
        for _ in range(X):
            cells = input()
            for y in range(Y):
                if cells[y] == '#':
                    results[z][y] = '#'
        input()  # 邪魔な -- を無視する
    
    for result in results[::-1]:
        print(*result, sep='')
    

    単純に x 軸を無視するとそれが答えになります。
    入力例はブロックを上から見た図になっています。これを下から順に積み上げていきます。

    この上から見たブロックを正面から見ると、

    ♯♯

    ・・
    ↑↑↑ ← 正面から見る
    ♯♯♯ ← ①

    という見え方になります。これが問題文にある絵図の一番下の段です。これをすべて見てみると、

    ♯♯・
    ♯♯
    ・・・
    ↑↑↑
    ♯♯・ ← ➁

    ♯♯・
    ♯♯
    ・・・
    ↑↑↑
    ♯♯・ ← ➂

    これを下から積み上げると、

    ♯♯・ ← ➂
    ♯♯・ ← ➁
    ♯♯♯ ← ①

    と、出力例1 と同じ結果となります。

    これをプログラムにします。

    初めに Y軸と同じ幅、Z軸と同じ高さの . で埋めたマップを作ります。

    results = [['.'] * Y for _ in range(Z)]
    

    ・・・
    ・・・
    ・・・


    for z in range(Z):
        for _ in range(X):
            cells = input()
            for y in range(Y):
                if cells[y] == '#':
                    results[z][y] = '#'
    

    Z軸の一番下の段からループします。三重ループともなると、とてもわかりにくくなりますね。😓 想像力の有無で難易度も上下するでしょう。

    X軸は一番奥からループします。ここで入力を受け取っています。

    Y軸は一番左のセルから順に cells[y]# の時は、新しく作ったマップ resultsresults[z][y]# を書き写します。これは X 軸を無視して正面から見える Y軸の部分を書き写しています。一度書き写された所に # があっても上書きします。

    【ループX】
    ♯♯♯ ← 1回目
    ♯♯・ ← 2回目上書き
    ・・ ← 3回目上書き
    ↑↑↑
    ♯♯♯ ← ループZ 1回目結果
    y0y1y2

    【ループX】
    ♯♯・ ← 1回目
    ♯♯・ ← 2回目上書き
    ・・・ ← 3回目
    ↑↑↑
    ♯♯・ ← ループZ 2回目結果
    y0y1y2

    【ループX】
    ♯♯・ ← 1回目
    ♯♯・ ← 2回目上書き
    ・・・ ← 3回目
    ↑↑↑
    ♯♯・ ← ループZ 3回目結果
    y0y1y2


    ループZ を終えると、新しく作ったマップには以下の様な形に書き写されています。

    ♯♯♯ ← ①
    ♯♯・ ← ➁
    ♯♯・ ← ➂
    result = [['#','#','#'], ['#','#','.'], ['#','#','.']]

    これを線対称に上下反転させて画面に平面出力すれば完了です。

    for result in results[::-1]:
        print(*result, sep='')
    
    ##.
    ##.
    ###

    ♯♯・ ← ➂
    ♯♯・ ← ➁
    ♯♯♯ ← ①
    result = [['#','#','.'], ['#','#','.'], ['#','#','#']]

    テキストだけで立体の解説をするの難しいよ。😅

    【おまけ】

    ループZ の書き方を次の様にすると、最後反転する必要がなくなります。

    X, Y, Z = map(int, input().split())
    results = [['.'] * Y for _ in range(Z)]
    
    for z in range(Z-1, -1, -1):  # ← ココ
        for _ in range(X):
            cells = input()
            for y in range(Y):
                if cells[y] == '#':
                    results[z][y] = '#'
        input()
    
    for result in results:  # ← 反転不要になる
        print(*result, sep='')
    
神経衰弱(オリジナル)

神経衰弱(オリジナル)

STEP: 1 神経衰弱(力試し編)

神経衰弱(力試し編)

    def turn_up_cards(turn, a, b, A, B):
        if t[a-1][b-1] == t[A-1][B-1]:
            return 2, turn
        else:
            return 0, turn + 1
    
    
    H, _, N = map(int, input().split())
    t = [list(map(int, input().split())) for _ in range(H)]
    L = int(input())
    
    points = [0] * N
    turn = 0
    for _ in range(L):
        a, b, A, B = map(int, input().split())
        pnt, turn = turn_up_cards(turn, a, b, A, B)
        points[turn % N] += pnt
    
    print(*points, sep='\n')
    

    なんだか不完全な問題ですね。😓 入力やテストケースではカードが取り除かれて存在しない場所が与えられていないようなので、カードを取り除く処理を作らなくても 100点が取れてしまいます。そういうところも読み解いてラクをしなさいという意図が込められているのならそうします。😊 正解率が高く、難易度が低すぎるのも、みんなこれに気付いたからでしょうか?😉

    この問題の処理は単純に、

    1. めくったカードの数が一致していれば2枚ゲット
    2. 一致しなければ次の人へ

    これだけで済みます。1番は関数の通りです。2番はちょっとわかりにくいことをしています。めくったカードが一致していなければ、取ったカードの枚数を 0 枚にして、turn + 1 で次の人に順番をまわしています。しかしこの後に、

    points[turn % N] += pnt
    

    「次のターンの人が持っているカード枚数に 0 を足す」という処理をしています。自分のに足せよ!😆
    結果オーライなのでまぁいいかと放置しています。やたらいい加減なプログラム例でした。😁

    なんかやる気しないなこの問題。解説もいい加減になってしまった。😓

名刺バインダー管理

名刺バインダー管理

FINAL問題 名刺バインダー管理

名刺バインダー管理

    n, m = map(int, input().split())
    p1 = -n * 2
    p2 = m % p1 * 2
    print(p1 - p2 + m + 1)
    

    自分で考えてプログラムしたものなのに、なぜこれで正しく動くのかよくわかっていません。😓
    もともと最初に提出した以下のプログラムから改善を重ねていたらこの状態に行き着いたのです。

    pocket, No = map(int, input().split())  # pocket = ポケットの数 , No = 名刺番号
    np2 = No % (pocket * 2)  # np2 = 係数
    
    if 0 < np2 <= pocket:  # 右ページ
        result = pocket * 2 - (np2 * 2 - 1) + No
    elif pocket < np2 < pocket * 2:  # 左ページ
        result = pocket * 2 - np2 * 2 + 1 + No
    elif np2 == 0:  # 係数が 0 の時
        result = No + (pocket * -2 + 1)
    
    print(result)
    

    pocket, m = map(int, input().split())
    n1 = pocket * 2
    n2 = m % n1  # n2 = 係数(ポケットの x 位置)
    
    ans = (-n1 if n2 == 0 else n1 - n2 * 2) + m + 1
    
    print(ans)
    

    n, m = map(int, input().split())
    p1 = -n * 2
    p2 = m % p1 * 2
    print(p1 - p2 + m + 1)
    

    わかりそうでわからん。🤣
    実際に数値を当ててみて証明してみようと思ったのですが・・・わかりませんでした。脳が考えることを拒絶している………。

    よって解説不可能です。ごめんなさい!😽
    むしろ証明できる方がいましたら私に詳しく解説してほしいものです。😅

    それにしても、組んだプログラムから数式に変換できるなんてすごいね!(自画自賛)

値の計算

値の計算

STEP: 1 値の計算

値の計算

    def serial(q):
        return resistor[q]
    
    def parallel(q):
        return 1 / sum(1 / resistor[c] for c in list(q))
    
    def calc_resistance_value(t):
        return sum([serial(q) if len(q) == 1 else parallel(q) for q in t])
    
    
    N = int(input())
    resistor = {s: int(w) for _ in range(N) for s, w in [input().split()]}
    _ = input()
    t = input().split()
    
    print(int(calc_resistance_value(t)))
    

    電気のことがわからなくても、問題の中で解き方が全て語られています。
    「分数に分数?」……それも書き方が書かれています。その割に正解率が低いですね。

    パッと思いついたやり方がこのプログラム例の方法です。それぞれの抵抗は辞書にすると後々簡単です。

    プログラムの中枢は calc_resistance_value() です。直列が serial() 関数で、並列が parallel() 関数です。

    def serial(q):
        return resistor[q]
    
    def parallel(q):
        return 1 / sum(1 / resistor[c] for c in list(q))
    
    def calc_resistance_value(t):
        return sum([serial(q) if len(q) == 1 else parallel(q) for q in t])
    

    アルファベットが一文字の時は直列である serial() 関数で、アルファベット二文字以上の時は parallel() 関数でそれぞれ処理し、計算結果を返します。それら計算結果はリストに収められ、そのリストの要素を全て足すと抵抗器の値が求められます。

    この時点では値がまだ浮動小数型となっていますので、ここで整数型に変換して画面に出力すれば完了です。


    ちなみに条件に『t_i (1 ≦ i ≦ M) の長さは 1000 以下である』とあるように並列時の抵抗が最大で1000個あるので、AB の文字と文字を分離する為に Python3 では必然的に list(q) という書き方が浮かぶと思います。この条件を見落として入力値の AB や問題文の絵図を見て「並列 = 抵抗は2つ」と先入観を抱いてしまった方々が正解率を下げてしまっているのかなと思います。スキルチェックではこういうところもすべてチェックして理解することが大切です。特にBランクは、問題をよく読んで理解してからプログラムに取り掛かったほうが早く解けると思います。よく読んでもうっかり勘違いをしていることに気付くのに数時間から数日かかったりすることもよくある 話です。😁

    あとは q の文字数が1つかそれ以外かで直列並列を振り分けられれば、他は問題文に書かれているやり方を参考にプログラムを組めば、形は違えども部分的にはプログラム例のようになると思います。

    list(q) はリストに変換しないで文字列のままでも大丈夫です。

    def parallel(q):
        return 1 / sum(1 / resistor[c] for c in q)
    

    参考になったかはわかりませんが、それでは STEP 2 以降、頑張ってください。👋

「地下アイドルの夢」

「地下アイドルの夢」

STEP: 1 地下アイドルの夢

「地下アイドルの夢」

    N, M = map(int, input().split())
    
    gappori_money = 0
    if N > 0 and M > 0:
        e = [list(map(int, input().split())) for _ in range(M)]
        profits = [v for values in e if (v:=sum(values)) > 0]
        gappori_money = sum(profits)
    
    print(gappori_money)
    

    問題が長かったわりに単純なプログラムになりました。最初、組み合わせ問題かと思って気が遠くなってました。😅

    これはライブごとの収益の見込みから、1円以上になるライブの収益を全て足すだけでした。

    profits = [v for values in e if (v:=sum(values)) > 0]
    

    これは今言った『ライブごとの収益の見込みから、1円以上になるライブ』のリストを作っています。

    gappori_money = sum(profits)
    

    これが『1円以上になるライブを全て足す』ところです。


    問題は「N や M が 0 の時」です。N と M の入力直後にこの条件の為の 文を書かないと、次の入力、

    e = [list(map(int, input().split())) for _ in range(M)]
    

    で、ランタイムエラーが発生してしまいます。

    EOFError: EOF when reading a line
    

    あっしがやっちめぇやした。😜テヘッ 😜テヘッ 😜テヘテヘッ

    それでは STEP 2 以降、頑張ってください。