LoginSignup
9
4

More than 3 years have passed since last update.

【AtCoder解説】PythonでABC198のA,B,C問題を制する!

Posted at

AtCoder Beginner Contest 198A,B,C問題を、Python3でなるべく丁寧に解説していきます。

ただ解けるだけの方法ではなく、次の3つのポイントを満たす解法を解説することを目指しています。

  • シンプル:余計なことを考えずに済む
  • 実装が楽:ミスやバグが減ってうれしい
  • 時間がかからない:パフォが上がって、後の問題に残せる時間が増える

ご質問・ご指摘はコメントツイッターまでどうぞ!
Twitter: u2dayo

よかったらLGTMしていただけると、私のやる気がでます!

目次

ABC198 まとめ
A問題『Div』
B問題『Palindrome with leading zeros』
C問題『Compass Walking』

個人的なおしらせ

ニートをやめて働き始めたので、もしかすると解説を書く時間を取りづらくなるかもしれません。やめるつもりはありませんが、睡眠時間を確保したいので、翌日や翌々日に書くことが増えるかもしれません。

また、D問題やE問題が簡単だった場合、需要がかなりありそうだと感じたため、余裕があればそちらの解説も書きたいと考えています。これは間違いなく当日中に書き終えられないので、後日書くことにします。(まだあんまり考えていませんが、たぶん別記事で書くと思います)

アプリ AtCoderFacts を開発しています

コンテストの統計データを見られるアプリ『AtCoderFacts』を作りました。
現在のところ、次の3つのデータを見ることができます。

- レート別問題正解率
- パフォーマンス目安
- 早解きで上昇するパフォーマンス

今後も機能を追加していく予定です。使ってくれると喜びます。

ABC198 まとめ

全提出人数: 8172人

パフォーマンス

パフォ AC 点数 時間 順位(Rated内)
200 AB---- 300 49分 6167(5968)位
400 AB---- 300 13分 5079(4880)位
600 ABC--- 600 97分 4156(3964)位
800 ABC--- 600 55分 3197(3005)位
1000 ABC--- 600 28分 2318(2126)位
1200 ABCD-- 1000 72分 1612(1424)位
1400 ABC-E- 1100 43分 1095(912)位
1600 ABCDE- 1500 95分 716(553)位
1800 ABCDE- 1500 73分 460(313)位
2000 ABCDE- 1500 58分 283(162)位
2200 ABCDE- 1500 47分 166(77)位
2400 ABCDE- 1500 39分 106(34)位

色別の正解率

人数 A B C D E F
3378 96.5 % 76.3 % 35.0 % 2.0 % 2.8 % 0.0 %
1429 99.4 % 97.9 % 67.1 % 10.7 % 11.3 % 0.0 %
1090 99.5 % 99.0 % 79.1 % 30.2 % 35.2 % 0.0 %
623 99.7 % 99.5 % 87.6 % 65.2 % 72.9 % 0.2 %
351 100.0 % 99.7 % 95.7 % 90.0 % 94.6 % 0.3 %
158 95.6 % 94.3 % 90.5 % 91.1 % 94.3 % 6.3 %
33 97.0 % 97.0 % 93.9 % 97.0 % 97.0 % 42.4 %
11 100.0 % 100.0 % 100.0 % 100.0 % 100.0 % 81.8 %

表示レート、灰に初参加者は含めず

私(うにだよ)の結果

screenshot.167.png

AGCで黄パフォとった分がほとんど吹っ飛んで泣いています。

Cは法則は一瞬でわかりましたが、どう実装するかで無限に悩んでしまいました。Eは瞬殺できましたが、焦ったせいでDでおかしなDFS方針を選んでしまって解けず、散々な結果に終わりました……。

A問題『Div』

問題ページA - Div
コーダー正解率:96.5 %
コーダー正解率:99.4 %
コーダー正解率:99.5 %

考察

A君のもらうお菓子の数 $X$ を決めると、B君がもらうお菓子の数は残り $N - X$ 個 になります。

A君が $1$ 個以上のお菓子もらう方法は、$1$ ~ $N$ 個の $N$ 通りあります。
ただし、A君が $N$ 個全部もらってしまうと、B君のお菓子が $0$ 個になってしまうのでダメです。

よって、A君が $1$ ~ $N-1$ 個のお菓子をもらう分け方 $N-1$ 通りが条件を満たすので、答えは $N-1$ です。

コード

N = int(input())
print(N - 1)

B問題『Palindrome with leading zeros』

問題ページB - Palindrome with leading zeros
コーダー正解率:76.3 %
コーダー正解率:97.9 %
コーダー正解率:99.0 %

考察

頭に"0"を何個つけるか全探索して、回文になるものを探します。

実装

まず、入力は文字列 $S$ として受け取ってください。このほうが、後々楽です。

頭に"0"をいくつかつけた文字列の作り方

forループを用いて、このように書きます。

『先頭に $0$ 個以上の $0$ をつける』とありますから、$0$ 個つける(空文字列をつける)場合も判定するように注意してください。

for i in range(100):
    Z = "0" * i  # i == 0なら、空文字列になります i == 3なら "000" です
    T = Z + S  # S の頭に、Zをくっつけます

回文判定

回文とは「ひっくり返しても自分自身と同じになる文字列」のことです。当たり前ですね。

つまり、文字列Tが回文か判定するには『文字列T』と『文字列Tを反転したもの』が同じであるかどうか見ればいいです。

スライスを用いて書くと、こうなります。非常に簡潔です。

if T == T[::-1]:
    # 回文だったときの処理

コード

S = input()


def judge():
    # Sは最大10桁ですから、頭につける0の数は9個まで試せば十分です。
    # ただ、そういう事を考えるのが面倒なので適当に100個程度まで試します

    for i in range(100):
        Z = "0" * i
        T = Z + S
        if T == T[::-1]:
            # 回文なのでTrueを返します
            return True

    # どうやっても回文にならないので、Falseです
    return False


if judge():
    print("Yes")
else:
    print("No")

C問題『Compass Walking』

問題ページC - Compass Walking
コーダー正解率:35.0 %
コーダー正解率:67.1 %
コーダー正解率:79.1 %

考察

問題文に到達不能である場合の出力指示がありません。実際、どのような場合でも必ず到達することが可能です。

1回移動する場合

$1$ 回 移動すると、距離が $R$ である座標であればどこにでもいけます。これは当然ですね。

2回移動する場合

$2$ 回 移動すると、${0}\le{d}\le{2R}$ であるような距離 $d$ の座標であれば、どこにでも移動することができます。

$d = 0$ は行って戻ってくるだけなので、やる必要はありません。
$d = R$ は $1$ 回移動するだけでもできるので、$2$回かけて移動するのは無駄です。
$d = 2R$ は、全く同じ方向に $2$ 回進めば良いです。

それ以外の場合を考えます。二等辺三角形を思い浮かべてみてください。

abc198c.png

この二等辺三角形を適当に回転させれば、どんな向きにでも進めます。

最適な移動方法

$1$ 回移動するパターン、$2$ 回移動するパターンのどちらでも、好きな向きに進むことができます。ですので、できる限りゴールの$(X,\ Y) $に向かって一直線に進むのが最適です。

つまり、原点から $(X,\ Y)$ までの距離 $D = \sqrt{X^2 + Y^2}$ だけが重要で、座標自体はどうでもいいです。

距離

原点から点 $(X,\ Y)$ までの距離を $D = \sqrt{X^2 + Y^2}$ とします。

また、$D÷R$ の商を$A$、余りを$B$とします。

パターン1: B = 0 の場合

もし余り $B=0$、つまり $D$ が $R$ で割り切れるなら、$A$ が 答えです。
最短距離で真っ直ぐ $A$ 回進むだけです。

パターン2: B > 0 の場合

$B\gt{0}$、$D$ が $R$ で割り切れない場合を考えます。

まず、最短距離で真っ直ぐ $A-1$ 回進みます。すると、残りの距離は $R + B$ です。$B$ は 『$D$ を $R$ で割った余り』なので、$B < R$ です。ということは、$R < R + B < 2R$ です。

先程書いた通り、$0 < d < 2R$ である座標は $2$ 回の移動で到達することができます。

よって、答えは $(A - 1) + 2 = A + 1$ 回です。

パターン1, 2をまとめてみる

パターン1とパターン2は $\lceil\frac{D}{R}\rceil$ 回 とまとめて書くことができます。$\frac{D}{R}$の小数点以下を切り上げた値という意味です。

コーナーケース: D < Rの場合

ただし、この問題にはコーナーケースがあります。距離 $D$ が、移動できる距離 $R$ より小さい場合です。

$\lceil\frac{D}{R}\rceil = 1$ になりますが、実際は $1$ 回の移動ではゴールを通り越してしまうので、到達できません。

距離が $0 < d < 2R$ の座標には $2$ 回で到達できますから、答えは $2$ 回です。

実装

浮動小数点数を扱うと誤差が出る恐れがありますが、今回の問題では制約が小さいために使ってもACできるので、素直に使うことにします。

コード

import math

R, X, Y = map(int, input().split())

D = math.sqrt(X ** 2 + Y ** 2) # (X ** 2 + Y ** 2) ** 0.5 でもいいです
if D < R:
    print(2)
else:
    print(math.ceil(D / R))
9
4
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
9
4