Time Limit: 2 sec / Memory Limit: 1024 MiB
第 1 章について
第 1 章では、プログラムの基本的な文法について説明します。
第 1 章をマスターすれば、理論上は全ての「計算処理」を行うプログラムを書ける程の知識が身につきます。
APG4bPython の取り組み方
プログラミングの勉強では手を動かすことが重要です。練習問題はできるだけ解くようにしましょう。
ただし、「説明文中のプログラムを何も考えずに写す」というのは時間の無駄にしかならないことが多いです。
用意されているプログラムを動かすときはコピー&ペーストを使いましょう。
Python の特徴と選ぶ理由
競技プログラミングを始めるにあたって、どの言語を使うかで悩む方も多いと思います。 AtCoder では言語選択は恒久的なものではなく、問題ごとに柔軟に使い分けることもできますが、多くの参加者はほとんどの問題でひとつのメイン言語を使い続けていることも事実です。
このページを見ている方は、多少なりとも Python で競技プログラミングを始めようか検討されていることと思います。以下では、メイン言語として Python を選ぶメリット・デメリットを紹介します。
Python を使うメリット
文法が直感的
トップページでも書きましたが、 Python は文法が直感的でコードが書きやすく可読性が高いと言われています。競技プログラミングでは良い解法を思いついても、時間内にコードに書き出せないと得点が得られません。また 1 分 1 秒でも早く正解した方が有利になります。このため、書きやすいという特徴は有利に働きやすいと言えます1。
多倍長整数
Python では多倍長整数が標準機能で簡単に扱えるため、問題によっては大きなアドバンテージになります。通常は煩雑な考察をすることを想定された問題でも、多倍長整数を使うことで、解法やコードが簡潔になることがあります。
Python を使うデメリット
実行速度
Python はインタプリタ言語であり、 C++ のようなコンパイル言語と比べるとどうしても実行速度が遅くなる傾向があります。
直近の AtCoder のコンテストでは、超上級者向けの問題の一部を除いて、コンテストで出題される問題のほとんどは Python (後述の CPython または PyPy)でも実行時間制限内に無理なく通すことができます。このため、解法は合ってるのに Python だから通せないということはほとんどありません。
もちろん、想定解よりも遅い解法を使う場合や Python が得意でない書き方をする場合などでは TLE (実行時間制限超過)になる可能性はあります。本プログラムでは、 Python を使う場合に TLE に引っかからないために気を付ける点についてもなるべく触れていく予定です(主に 2 章以降になる予定です)。
CPython と PyPy
AtCoder では、 Python コードの実行環境として CPython と PyPy が用意されています。 CPython と PyPy はいずれも Python の実行環境で、多くの場合は全く同じコードで全く同じアウトプットを返しますが、一部挙動が異なる場合があります。それぞれの特徴は以下の通りです。
- CPython は Python の公式の実装です。
- PyPy は JIT(Just-In-Time)コンパイル機能を持っています。 JIT コンパイルにより実行時にコードを機械語に変換することで、 CPython に比べて高速になることが多いです。
AtCoder における競技プログラミングでは、一部の例外を除き、基本的には PyPy で提出する方が実行時間制限の関係で有利になることが多いです。
問題
提出練習
プログラミングの文法の説明に入る前に、自動採点システム(ジャッジ) 2 の使い方を確認しましょう。ジャッジへの提出フォームは、このページの一番下にあります。
提出言語の選択
CPython で提出する場合は "Python (CPython 3.11.4)" を、 PyPy で提出する場合は "Python (PyPy 3.10-v7.3.12)" を選択しましょう。「言語」欄を押して出てくるボックスに言語名の一部("py" など)を入力すると探しやすくなります。 3
ソースコード
「ソースコード」欄にはプログラムのソースコードを入力します。
ここでは、次のプログラムをコピー&ペーストしてみましょう。
コピー&ペーストの仕方:
↓の右上にある「Copy」ボタンをクリック→「ソースコード」のテキストを入力する場所で右クリック→「貼り付け」をクリック
print("Hello, world!")
提出・結果確認
言語選択とソースコードの入力ができたら、「提出」ボタンを押してください。
ページが切り替わり、WJ と表示されているところが AC に変われば提出成功です。
- WA や RE と表示されてしまった場合、正しくコピー&ペーストできているか確認し、再提出してください。
- CE と表示されてしまった場合、「言語」が正しく選択されているか確認してください。 "Python (CPython 3.11.4)" または "Python (PyPy 3.10-v7.3.12)" が選択されているかどうか確認し、再提出してください。
CPython と PyPy の一方で AC が得られたら、もう片方でも提出してみましょう。全く同じコードで AC が得られることが確認できると思います。
コードテスト
ジャッジは、出力結果が正しいかどうかを判定してくれますが、思い通りの結果が得られない場合など、自分のコードがどのような出力をしているのか確認したいこともあると思います。「コードテスト」を使うとプログラムがどのような出力をしているか確認することができます。コードテストはページ上部のリンクから飛ぶことができます。
コードテストの使い方は、上で説明したジャッジの使い方に似ていますが、下記のとおりです。
- 提出言語を選択する
- ソースコードを記載する
- 「標準入力」欄に入力(後述)を記載する
- 実行ボタンを押す
「入力」については 1.05. 節で扱います。このページの Hello, world!
を出力するような例では入力はありませんので、「標準入力」欄は空欄で構いません。
Time Limit: 0 msec / Memory Limit: 0 KiB
キーポイント
- 出力には
print
を用いるprint
は文字列や数値を出力できる- 複数の要素を改行区切り、スペース区切りで出力することも可能
- コメントを書く際は
#
や"""
を用いる - 全角スペース、行頭の半角スペースに注意
出力を行うプログラム
文字列の出力
Python においてプログラムは上から順に一行ずつ実行されます。以下は前節で提出した "Hello, world!" という文字列を出力する(画面に表示する)プログラムです。
print("Hello, world!")
実行結果
Hello, world!
このプログラムは print
という機能2で出力を行っています。print
に続けて半角の丸括弧内に要素を記述することで、記述した要素を出力することができます。
この例では "Hello, world!"
という文字列を出力しています。Python では " "
で囲った部分は文字列として扱われます。print
は文字列を受け取るとその内容をそのまま出力します。
異なる文字列を出力したければ、" "
の中に入れる文章を書き換えれば良いです。
print("Pythonだいすき")
実行結果1
Pythonだいすき
数値の出力
print
は文字列以外にも様々な要素を受け取ることができます。以下は数値を出力するプログラムです。
print(123)
実行結果
123
この例では 123 という数値(整数)を出力しています。Python では数字から成る要素は数値として解釈されます。print
関数は数値も文字列同様に値をそのまま出力することができます。
なお、ここで print("123")
と書いても同じ実行結果を得ることができます。
複数の出力
改行区切りの出力
複数の print
を続けて記述するとどうなるでしょうか。
print("Hello, world!") print(123)
実行結果
Hello, world! 123
Python は記述したプログラムを上から順に実行します。また、print
は実行のたびに出力の最後に改行を行います。この結果上記のように 2 つの出力要素が順番に 2 行にわたって出力されます。
スペース区切りの出力
print
は複数の要素を受け取ることができます。
print("Hello, world!", 123)
実行結果
Hello, world! 123
このプログラムでは半角カンマ ,
区切りで 2 つの要素を print
に渡しました。この場合、渡した要素を順にスペース区切りで 1 行で出力されます。
なお、要素が 3 つ以上でも同様にすべての要素を順にスペース区切りで出力できます。コードテストから試してみてください。
print
の機能を用いる方法
改行区切りで出力する別の方法として、以下のように print
の機能を用いる方法があります。
print("Hello, world!", 123, sep="\n")
実行結果
Hello, world! 123
このプログラムでは print
に sep="\n"
という要素を追加で指定しています。この sep=
は「複数要素の間に挿入する文字列」を指定します。この例だと 2 つの要素の間に改行文字 "\n"
を挿入することになり、上記のような実行結果が得られます。
スペース区切りで出力する別の方法もあります。以下のように記述します。
print("Hello, world!", end=" ") print(123)
実行結果
Hello, world! 123
このプログラムでは print
に end=" "
という要素を追加で指定しています。この end=
は「出力後に挿入する文字列」を指定します。この例だと出力後に半角スペースを挿入することになり、上記のような実行結果が得られます。
スペース区切り、改行区切りそれぞれについて 2 通りの方法を紹介しました。難しく感じる場合、慣れないうちは自分の分かりやすい方法だけを使っていれば問題ありません。
もし print
の動作仕様について正確な情報が知りたい場合は Python の公式ドキュメント を参照してみてください。
より複雑な要素の出力
より複雑な要素を出力したい場合、print
の書き方を工夫するのではなく、出力したい要素を文字列として構築し、その文字列を print
で出力する、という方法を取ることになります。
本教材の後半で文字列の処理を学べば、より複雑な出力を行うことができるようになります。
コメント
コメントは人間が「プログラムがどういう動作をしているか」等のメモ書きをプログラム中に残しておくための機能です。
プログラムとしての意味はないので、書いても動作に影響はありません。
2 種類のコメントの書き方
例を見てみてみましょう。
# 123 # 456 # と出力するプログラムです print(123) print(456) """ print は受け取った要素を出力する 最後に改行が自動で挿入される """
実行結果
123 456
1-3行目に #
から始まる行はコメントで、プログラムの動作には影響しません。
7-10行目の """
で挟まれた部分も同様にコメントです3。
コメントアウト
一時的に実行させたくない部分をコメントにしてしまうことで、その部分のプログラムを無効化するというテクニックがあります。このテクニックは コメントアウト と呼ばれ、よく使われます。
# print(123) print(456)
実行結果
456
1行目の処理はコメント扱いで実行されないため、123
は出力されません。
123
を出力したくなった場合はコメントを解除すればよい (#
を消せば良い) ため、書き直しの手間は小さいです。
注意点
全角スペース
Python は全角文字を扱うことは可能ですが、プログラムの中に全角スペース「 」が入っているとエラーになってしまいます。発見が難しいため注意してください。
書き出し位置
Python では各行の書き出し位置に規則が存在します。特に、今回書くような単純なプログラムではすべての行が行の左端に位置している必要があります。
行の先頭には (全角スペースは勿論ですが) 半角スペースも記述しないように注意してください。
問題
リンク先の問題を解いてください。
EX1.コードテストと出力の練習
-
人によります。 ↩
-
より具体的には、組み込み関数という Python が提供する仕組みになります。組み込み関数については 1.12.組み込み関数で扱います。 ↩
-
厳密には複数行にわたる文字列ですが、コメントを書くために利用されることも多い記法です。 ↩
Time Limit: 0 msec / Memory Limit: 0 KiB
キーポイント
- Python プログラムを書く際には行の書き出し位置・改行などのルールに従う必要がある
- 読み込み時エラーとは Python プログラムの記述ルールに従っていない場合に起こるエラーのことで、プログラム自体が実行されない
- 実行時エラーとはプログラムの処理が継続できない事象が生じた場合に起こるエラーのことで、プログラムが異常終了する
- 論理エラーとはプログラムの動作が意図したものと異なる場合に起こるエラーのことで、プログラムの実行は問題なく終了するため発見が難しい
- 読み込み時エラーや実行時エラーが出たときはエラーメッセージをよく読み、少しずつ慣れていくことが重要
プログラムの書き方
プログラムは通常の文章を書くときには無いルールが存在します。他の言語と異なる Python 特有の事情も存在しますので、基本的なルールを紹介していきます。
行の書き出し位置とインデント
Python は各行の書き出し位置を揃える必要があります。理由も無く行の書き出し位置がずれるとエラーになるため、行の先頭に余分なスペースが入らないように注意してください。
逆に、後の節で扱う「構文を表すキーワード + コロン」を記述した場合、次の行では書き出し位置を右にずらす (インデントする) 必要があります。
参考までに、インデントしている部分を含むコードの例を紹介します (現時点で意味が分かる必要はありません)。
# 何もない行は左詰め print("hoge") def function(): # 関数定義のコロンの後、インデントする # 複数行記述する場合、ずっとインデントしたまま print("foo") return 0 for i in range(10): # for 文のコロンの後、インデントする print("bar") for j in range(10): # 二重 (以上) にインデントすることもある print("baz")
C++などの他の言語では見やすくするためにインデントを行いますが、Python ではルールとして強制的にインデントを行う点が特徴的です1。
改行について
Python では実行文2同士を区切るために改行もしくはセミコロン ;
を使用します。
今まで print
を複数記述する際には必ず改行をはさんでいましたが、次のようにセミコロン ;
を使って複数の実行文を 1 行にまとめて記述することも可能です。
print("hello"); print(123)
実行結果
hello 123
逆に、1 つの実行文を複数の行に分けて記述したい場合、バックスラッシュ \
を用いて次のように書くことができます。次の例は 1 から 8 までの整数を足し合わせた結果を表示するプログラムです。足し合わせる数が多いため、途中でバックスラッシュを用いて改行をはさんでいます。
res = 1 + 2 + 3 + 4 \ + 5 + 6 + 7 + 8 print(res)
実行結果
36
一部のケースではバックスラッシュなしで改行をはさむことができます。具体的には、丸カッコ ()
や四角カッコ []
、波カッコ {}
などの内側です。次のような書き方のイメージになります(意味は気にしなくて構いません)。
print( 123, "hoge", "foo", sep="\n" ) lst = [ 10, 20, 30 ]
カッコの中身は記述が長くなりやすいため、改行を使うことで読みやすいコードになります。
スペースについて
Python では記述する要素同士の切れ目には半角スペースを何個でも置くことができます。
print(123, 456 , 789)
注意として、半角スペースでなく全角スペースを記述するとエラーとなってしまうので気をつけてください。
プログラムのエラー
プログラムは書き終わったら完成ではありません。動かしてみてその動作が正しいことを確認して、はじめて完成と言えます。
しかし書き終えたばかりのプログラムを実行しようとすると、たいてい何らかのエラーが発生します。そのときにエラーの原因を理解して修正できることも重要なスキルです。
Python プログラムのエラーは次の3つに大別できます。
- 読み込み時エラー
- 実行時エラー
- 論理エラー
それぞれについて説明します。
読み込み時エラー
書いたプログラムの文法にミスがあるときに発生するエラーです。
全角スペースがプログラム中に入り込んだり、インデントが不正である場合、コードを読み込んだ時点でエラーになります。
プログラミング言語では「文法」が厳密に決められています。
日本語などの人間が使う言語では、文法的に少し崩れた文でも意図が通じますが、プログラミング言語ではそうはいきません。
読み込み時エラーが起きた場合、プログラムは一切動作しません。AtCoder のジャッジの実行結果は CE となります。
実行時エラー
「プログラムを動かす」ことを「プログラムを実行する」といいます。
実行時エラーとは、プログラムの文法に間違いはなかったが、内容に致命的な間違いがあったときに発生するエラーです。
具体的には3÷0のように、0で割り算を行ってしまった場合や、書き間違い (print
を pritn
と書くなど) で存在しない要素を記述した場合などに発生します。
スマホアプリやゲーム等が強制終了してしまったとき、多くの場合実行時エラーが発生しています。
実行時エラーが起きた場合、実行時エラーが起きる直前までプログラムは動作しますが、エラー以降は動かなくなってしまいます。AtCoder のジャッジの実行結果は RE となります。
論理エラー
論理エラーとは、プログラムは一見正しく動作しているが、その動作が実は正しくないときに発生するエラーです。
例えば、「300円のクッキーと100円のアメを買ったときに払うお金」を計算するプログラムでは300 + 100と計算するべきですが、間違って300 - 100としてしまった場合などは論理エラーに当たります。
論理エラーは勘違いで生まれたり、タイピングのミスで発生したりと様々です。
論理エラーは一見問題なくプログラムが動作しますが、意図と異なる結果を返すため、発見することが難しくなりやすいです。AtCoder のジャッジの実行結果は(偶然実行結果が合うということが無い限りは) WA となります。
エラーの例
この時点でよく遭遇すると思われるエラーを紹介します。
全角スペース
次のコードには文末に全角スペースが入っており、SyntaxError
という読み込み時エラーとなっています。エラー文の中で U+3000
と記述されているものが全角スペースに相当します。記述されている箇所も教えてくれるため、修正は容易です。
print(123) # (123) の後に全角スペースがある
File "Main.py", line 1 print(123) ^ SyntaxError: invalid non-printable character U+3000
カッコの閉じ忘れ
開きカッコに対応する閉じカッコがない場合、SyntaxError
となります。
print(123
File "Main.py", line 1 print(123 ^ SyntaxError: '(' was never closed
インデントのミス
理由もなくインデントしてしまうと IndentationError
という読込み時エラーが発生します。
print(123) print(456)
Sorry: IndentationError: unexpected indent (Main.py, line 2)
スペルミス
書き間違いなどで存在しない名称3を記述してしまった場合、NameError
という実行時エラーになります。次のエラーでは pritn
という名称は存在しないということと、print
の書き間違いではないか?というヒントを与えてくれています。
pritn(123)
Traceback (most recent call last): File "/judge/Main.py", line 1, in <module> pritn(123) ^^^^^ NameError: name 'pritn' is not defined. Did you mean: 'print'?
エラーへの対処
プログラムにエラーはつきものなので、うまく付き合う必要があります。以下でエラーへの対処方法、心構えについて紹介します。
エラーをよく読む
Python の場合末尾にエラーの要点が書かれていることが多いです。大量のエラーが表示された場合読みたくなくなりますが、まずエラーの最後を見て大意がつかめないかトライしてみてください。
具体的な行数や箇所の表示を見て不自然な点が無いか確認してみるのもよいでしょう。
検索する
初心者の段階で遭遇するエラーはよくあるものが多いので、検索すればすぐにわかるケースも多いと思われます。
検索する場合はエラーの末尾の部分など、自分の環境固有の記述が含まれない部分を検索ワードに設定するとよいでしょう。
エラーの利点?
書くたびにエラーが出てうまく動かないと嫌な気持ちになるかもしれませんが、考えようによってはミスを Python の実行プログラムが教えてくれているだけ有益ともとれます。
前述の論理エラーのような「表示されないエラー」はミスが明らかにならないため発見が遅れ、大きなバグに繋がってしまうこともあります。
問題
リンク先の問題を解いてください。
Time Limit: 0 msec / Memory Limit: 0 KiB
キーポイント
演算子 | 計算内容 |
---|---|
+ |
足し算 |
- |
引き算 |
* |
掛け算 |
// |
割り算(整数) |
/ |
割り算(実数 1 ) |
** |
べき乗 |
% |
割った余り |
(注)割り算の記号がふたつありますが、 //
は整数での除算(切り捨て除算)、 /
は実数(float)での除算です。違いについては後の例で説明します。
足し算・引き算・掛け算
Python で足し算・引き算・掛け算を行う場合は、 +
-
*
を使います。
print(7 + 2) # 9 print(7 - 2) # 5 print(7 * 2) # 14
実行結果
9 5 14
割り算
割り算については少し注意が必要です。 //
は整数範囲での割り算を計算し、切り捨てされた結果を整数として返します。一方で、 /
は実数で計算した結果を(次節1.04変数と型で扱う float 型で)返します。
print(7 // 2) # 3 print(7 / 2) # 3.5
実行結果
3 3.5
切り捨て除算は、負の数を割った場合も小さくなる方向(絶対値が大きくなる方向)に丸められます。
print(-7 // 2) # -4 print(-7 / 2) # -3.5
実行結果
-4 -3.5
べき乗
べき乗の計算(指数計算)は **
を使います。次のプログラムでは、 7 の 2 乗である 49 が出力されます。
print(7 ** 2)
実行結果
49
剰余演算
最後に、剰余演算(割った余りを計算する演算)です。 7 を 2 で割った余りは 1 なので、以下のプログラムでは 1 が出力されます。
print(7 % 2)
実行結果
1
負の数の切り捨て除算は、小さい方(絶対値が大きい方)に丸められると説明しましたが、剰余演算もそれと整合的に計算されます。より具体的には、正の整数の除算では a を b で割った商を q 、余りを r とすると a = b \times q + r\ (0\le r<b) が成立しますが、 a が負の数の場合も b が正であればこれが成立します。
例えば -11 を 5 で割ると -3 あまり 4 という計算になります。上の式に当てはめると -11 = 5 \times (-3) + 4 が成立することが確認できます。
print(-11 // 5)
print(-11 % 5)
実行結果
-3 4
演算子の優先順位
演算子には優先順位があります。
例えば 1 + 2 * 3
のような式を書いた場合、先に 2 \times 3 が計算された後に 1 が足され、計算結果は 7 になります。 *
の優先順位が +
より高いということです。
上で紹介した演算子の優先順位は次のとおりです。
優先順位 | 演算子 |
---|---|
高 | ** |
中 | * // / % |
低 | + - |
print(3 + 4 * 5) # 4 * 5 を先に計算する print(5 * 3 ** 2) # 3 の 2 乗を先に計算する
実行結果
23 45
同じ優先順位の演算子が並んでいる場合は、左から順に計算されます。
括弧 ( )
を使うと、括弧で括った部分が先に計算されます。
print(4 * (1 + 2)) # 括弧の中の 1 + 2 を先に計算する print(3 * 5 // 2) # 3 * 5 の結果を先に計算する
実行結果
12 7
注意点
ゼロ除算
ゼロで割り算を行おうとするとランタイムエラーになります。
次のコードはいずれもランタイムエラーになり、 AtCoder のジャッジだと RE が表示されてしまいます。
print(3 / 0)
print(3 // 0)
print(3 % 0)
割り切れる場合の実数除算
割り算をする際、たとえば 6 \div 3 のように割り切れる計算の場合、数学的には切り捨て除算と実数除算は同じ結果になりそうです。 Python では割り切れる場合であっても結果は少し異なります。具体的には、次節1.04変数と型で説明する「型」が変わます。
print(6 // 3) print(6 / 3)
実行結果
2 2.0
この例では、(数学的な値としては)いずれも 2 と等しいですが、場合によっては有効桁数の関係で値が変わることもあります。競技プログラミングでもこれによる失敗をするケースがあります。整数範囲で考えている場合は整数除算 //
を使うのが無難です。
発展的な内容
負の除数による除算
競技プログラミングでは敢えて除数を負にする必要があるケースはあまり見かけませんが、思わぬ罠になる可能性があるので、プログラムの挙動について説明しておきます。
a を b で割った商を q 、余りを r とします。 b が正のときは a = b \times q + r\ (0\le r<b) が成立すると説明しましたが、除数 b が負のときは余りの範囲も負の方向になり、 a = b \times q + r\ (b < r \le 0) を満たすように商・余りが決まります。
11=(-5)\times(-3)+(-4) より、11 を -5 で割った商は -3 、余りは -4 となります。
-11=(-5)\times 2+(-1) より、-11 を -5 で割った商は 2 、余りは -1 となります。
print(11 // -5)
print(11 % -5)
print(-11 // -5)
print(-11 % -5)
実行結果
-3 -4 2 -1
divmod
a を b で割った商は a // b
、余りは a % b
で取得できることは既に学びましたが、 divmod
関数を使うことでこれらを同時に取得することもできます。
具体的には、 divmod(a, b)
で a を b で割った商と余りのタプルを返します。「タプル」についてはまだ扱っていないので詳細は省略しますが、次のようなコードで取得できることを覚えておけば十分です。
q, r = divmod(20, 7) print(q) print(r)
実行結果
2 6
問題
リンク先の問題を解いてください。
Time Limit: 0 msec / Memory Limit: 0 KiB
キーポイント
型 | 書き込むデータの種類 |
---|---|
int | 整数 |
float | 実数(浮動小数点数) 1 |
str | 文字列 |
変数について
変数は「メモ」だと考えてください。一度使ったデータをまた後で使いたいときのために、名前を付けてメモをして覚えておくというものです。
「変数の名前」のことを 変数名 と言います。次の例では、整数 10 というデータを name
という変数に格納(メモ)して、あとから呼び出して使用しています。
name = 10 print(name) print(name + 2) print(name * 2)
実行結果
10 12 20
型について
「データの種類」のことを 型(かた) と言い、変数はいずれかの型を持ちます。
なお、前節までは変数を使わず print(123)
などのようにプログラムに直接整数などを書き込んでいましたが、実はこの 123
などにも型があり、この場合は整数型になっています。
以下に Python でよく使う型を挙げます。
int(整数型)
3
や -10
などの整数を表すことができます。
float(浮動小数点数型)
3.5
などの実数 1 を表すことができます。
str(文字列型)
"a"
や "Hello"
などの文字列を表すことができます。
代入
変数には値を代入することができます。代入するときは代入演算子「=
」を使い、左側に代入先の変数を、右側に代入する値を書きます。複数回代入すると、前の値が上書きされます。
x = 3 # 変数 x に整数 3 が代入される y = -7.3 # 変数 y に浮動小数点数 -7.3 が代入される z = "ABC" # 変数 z に文字列 "ABC" が代入される x = 10 # 変数 x に 10 が代入される(上書きされる)
値が代入された変数は、以下のように呼び出すことで使うことができます。
x = 3 y = -7.3 z = "ABC" print(x * 2) print(y * 3) print(z)
実行結果
6 -21.9 ABC
=
の右側は、変数や演算子を含んでいても構いません。
x = 3 y = x # y に x の値である 3 が代入される z = x * 10 + 2 # x * 10 + 2 の計算結果が代入される print(y) print(z)
実行結果
3 32
なお Python の =
は代入を意味していて、「等しい」という意味ではないということに注意してください。
指数表記
float 型では指数表記による記述もできます。絶対値が大きいまたは小さい数の場合は、 print
関数で出力した結果も指数表記になります。
x = 1.23e5 y = 3.5e-2 z = 2.7e-10 print(x) print(y) print(z)
実行結果
123000.0 0.035 2.7e-10
変数の型の宣言について
勘の良い方は、変数がどの型になるのかを書かなくて良いのか気になったかもしれません。実は Python ではユーザーが変数の型を指定(宣言)する必要はなく、プログラムで自動的に判定されます。具体的には、代入するもの(=
の右側)の型によって決まります。このため、同じ変数を別の型で上書きするということもできます。
x = 3 print(x) x = "AtCoder" print(x)
実行結果
3 AtCoder
複合代入演算子
「x
に 5 を足す」のような、計算結果を同じ変数に代入する操作は、 x += 5
のように書くことができます。これは x = x + 5
と書いたことと同じ結果になります。
x = 2 x += 5 print(x)
実行結果
7
+=
のような演算子を 複合代入演算子 と言います。前節で扱った演算子は、すべて =
を付けることで複合代入演算子として使えます。
複合代入演算子 | コード例 | 複合代入演算子を使わない書き方 |
---|---|---|
+= |
x += 3 |
x = x + 3 |
-= |
x -= 3 |
x = x - 3 |
*= |
x *= 3 |
x = x * 3 |
//= |
x //= 3 |
x = x // 3 |
/= |
x /= 3 |
x = x / 3 |
**= |
x **= 3 |
x = x ** 3 |
%= |
x %= 3 |
x = x % 3 |
例えば次のように書くことができます。
x = 2 x += 5 # 7 x *= 3 # 21 x //= 2 # 10 x **= 3 # 1000 x %= 300 # 100 x -= 20 # 80 print(x)
実行結果
80
複数の変数への代入
変数 x
に 2 を、変数 y
に 3 を代入するという操作を同時に行いたいときは、カンマ(,
)で区切って次のように書くことができます。
x, y = 2, 3 print(x) print(y)
実行結果
2 3
これを使うと、 x
と y
の値を入れ替える(Swap する)という操作も簡単に書くことができます。
x = 2 y = 3 x, y = y, x # x と y を入れ替える print(x) print(y)
実行結果
3 2
有効数字・範囲
int 型
Python の int 型は 多倍長整数 と呼ばれていて、多くのプログラミング言語の int 型のような大きさの制限(32 bit や 64 bit など)がありません。例えば、 3 の 1000 乗は 478 桁のとても大きな整数ですが、下記のように簡単に計算することができます 2 。
print(3 ** 1000)
実行結果
1322070819480806636890455259752144365965422032752148167664920368226828597346704899540778313850608061963909777696872582355950954582100618911865342725257953674027620225198320803878014774228964841274390400117588618041128947815623094438061566173054086674490506178125480344405547054397038895817465368254916136220830268563778582290228416398307887896918556404084898937609373242171846359938695516765018940588109060426089671438864102814350385648747165832010614366132173102768902855220001
float 型
float 型では、 10 進法で 15~16 桁程度(2 進法で 53 ビット)の有効数字で計算されます。これよりも高い精度が求められる場合は予期せぬ結果になることがあるので注意が必要です。
print(100 / 3)
実行結果
33.333333333333336
なお、浮動小数点数で表せる最大の数は約 1.8\times 10^{308} 程度、最小の数は約 -1.8\times 10^{308} 程度、最小の正の数は約 2.2\times 10^{-308} 程度です。
発展的な内容
異なる型同士の演算
前項で四則演算などの演算を学びました。その際は細かく触れませんでしたが、元の変数の型や値によって計算結果として返される型も自動的に決まります。異なる型同士の演算の場合は(同じ型同士の演算であっても場合によっては)注意が必要になります。ここですべてのパターンを網羅することはできませんが、いくつか例を挙げておきます。
int 型同士の和・差・積
整数(int)型同士の和・差・積は整数(int)型になります。
a = 7 b = 2 print(a + b) # 整数型の 9 print(a - b) # 整数型の 5 print(a * b) # 整数型の 14
実行結果
9 5 14
float 型同士の和・差・積
浮動小数点数(float)型同士の和・差・積は浮動小数点数(float)型になります。
a = 3.2 b = 2.5 print(a + b) print(a - b) print(a * b)
実行結果
5.7 0.7000000000000002 8.0
int 型と float 型の和・差・積
片方が int 型、片方が float 型の場合の和・差・積は float 型になります。 3.5 \times 4 は数学的には整数 14 になりますが、 Python のプログラムは float 型として返します。
a = 3.5 b = 4 print(a + b) print(a - b) print(a * b)
実行結果
7.5 -0.5 14.0
int 型同士のべき乗
a
と b
がともに int 型のとき、 a ** b
の型はどうなるでしょうか。簡単のため a
はゼロではないとします。
この場合は b \ge 0 の場合は int 型、 b < 0 の場合は float 型になります。
print(2 ** 3) print(2 ** -3)
実行結果
8 0.125
問題
リンク先の問題を解いてください。
EX4.◯年は何秒?
-
前節1.03四則演算と優先順位の注にも書きましたが、正確には実数ではなく浮動小数点数を表します。簡単のためここでは実数と記載しています。 ↩↩
-
ただしあまりに大きい場合は実行時間やメモリの関係で計算が行えない場合があります。手元環境で実行する際など、 PC の限界を超えるとクラッシュしてしまう可能性があるので注意してください。 ↩
Time Limit: 0 msec / Memory Limit: 0 KiB
キーポイント
変数 = input()
で入力を文字列として受け取ることができる- 整数として受け取るためには
変数 = int(input())
とする
- 整数として受け取るためには
- 1 行をまとめてひとつの文字列として受け取る
- 空白で区切って複数の文字列として受け取るためには
変数1, 変数2 = input().split()
などとする
- 空白で区切って複数の文字列として受け取るためには
入力の受け取り方
プログラムの実行時にデータの入力を受け取る方法を見ていきましょう。
「入力機能」を使うことにより、プログラムを書き換えなくても与える入力を変えることで様々な計算を行えるようになります。
入力を受け取るには input()
を使います。
次のプログラムは、入力として受け取った文字列を 3 回出力するプログラムです。
s = input() print(s) print(s) print(s)
入力
Pythonだいすき
実行結果
Pythonだいすき Pythonだいすき Pythonだいすき
文字列以外の入力
入力を文字列以外の形式で扱いたいときは、入力を受け取った後に変換する必要があります。
次のプログラムは、入力として受け取った整数を 10 倍したものを出力するプログラムです。
# 入力を文字列として受け取る a = input() # 文字列として受け取った入力を整数に変換する a = int(a) print(a * 10)
入力
5
実行結果
50
上のプログラムは、以下のように「入力を文字列として受け取り整数に変換する部分」をまとめて書くこともできます。
# 入力を整数として受け取る a = int(input()) print(a * 10)
入力
5
実行結果
50
入力を小数として受け取りたい場合も、float()
を使って以下のように書くことができます。
# 入力を小数として受け取る a = float(input()) print(a * 10)
入力
3.14
実行結果
31.4
複数行の入力
input()
は 1 行の入力を受け取るものなので、input()
を行数分書くことで複数行の入力を受け取ることができます。
s = input() a = int(input()) print(s, a * 10)
入力
Hello 5
実行結果
Hello 50
空白区切りの入力
前述のように、input()
は 1 行の入力を文字列として受け取ります。
s = input() print(s)
入力
10 20 30
実行結果
10 20 30
そのため、空白区切りの入力を受け取ると、上のように "10 20 30"
というひとまとまりの文字列として受け取ってしまいます。
そこで、次のプログラムのように input()
で受け取った文字列を split()
で空白区切りで分解することで別々の変数に格納することができます。
a, b, c = input().split() print(a) print(b) print(c)
入力
10 20 30
実行結果
10 20 30
このままだと文字列として扱っていますが、次のプログラムのように int()
を使って整数に変換することもできます。
a, b, c = input().split() a = int(a) b = int(b) c = int(c) print(a * 10) print(b * 100) print(c * 1000)
入力
10 20 30
実行結果
100 2000 30000
上のプログラムは、以下のように「入力を空白区切りで分割し整数に変換する部分」をまとめて書くこともできます。
map(int, input().split())
の意味については今の時点では理解する必要はありません。このまま覚えてしまっても良いでしょう。
a, b, c = map(int, input().split()) print(a * 10) print(b * 100) print(c * 1000)
入力
10 20 30
実行結果
100 2000 30000
また、split()
は以下のように区切り文字を指定することもできます。
これまでのように何も指定しない場合は、空白文字で区切られます。
a, b, c = input().split(":") print(a) print(b) print(c)
入力
10:20:30
実行結果
10 20 30
問題
リンク先の問題を解いてください。
Time Limit: 0 msec / Memory Limit: 0 KiB
キーポイント
if
文を使うと、「指定した条件に当てはまるときだけ処理をする」というプログラムを書けるelif
やelse
を使うと、「条件に当てはまらなかったとき」の処理を書ける
if 条件式1: 処理1 (条件式1に当てはまるときに実行) elif 条件式2: 処理2 (条件式1に当てはまらず、条件式2に当てはまるときに実行) else: 処理3 (条件式1にも条件式2にも当てはまらないときに実行)
- 一般的な比較演算子のまとめ
演算子 | 意味 |
---|---|
x == y |
x と y は等しい |
x != y |
x と y は等しくない |
x > y |
x は y より大きい |
x < y |
x は y より小さい |
x >= y |
x は y 以上 |
x <= y |
x は y 以下 |
- 一般的な論理演算子のまとめ
演算子 | 意味 | 真になるとき |
---|---|---|
not 条件式 |
条件式 の真偽の反転 |
条件式 が偽 |
条件式1 and 条件式2 |
「条件式1 が真」かつ「条件式2 が真」 |
条件式1 と 条件式2 のどちらも真 |
条件式1 or 条件式2 |
「条件式1 が真」または「条件式2 が真」 |
条件式1 と 条件式2 の少なくとも片方が真 |
if 文
if 文を使うと、ある条件が正しいとき(当てはまるとき)だけ処理をするプログラムを書くことができます。
書き方は次のとおりです。
if 条件式: 処理
条件式が正しいとき、:
以降のインデントが下がった部分の処理が実行され、条件式が正しくないとき、処理は飛ばされます。
次のプログラムは、入力の値が 10 より小さければ「x は 10 より小さい
」と出力した後「終了
」と出力します。入力の値が 10 より小さくなければ「終了
」とだけ出力します。
x = int(input()) if x < 10: print("x は 10 より小さい") print("終了")
入力 1
5
実行結果 1
x は 10 より小さい 終了
入力 2
15
実行結果 2
終了
この例では、まず x = int(input())
の部分で入力された値を整数として変数 x
に代入しています。
重要なのはその後です。
if x < 10: print("x は 10 より小さい")
この部分は、「もし x < 10
(x
が 10 より小さい)ならば x は 10 より小さい
と出力する」という意味になります。
最後に print("終了")
の部分で 終了
と出力します。
x
が 10 より小さくない場合、以下の部分は飛ばされます。
print("x は 10 より小さい")
そのため、2 つ目の実行例では 終了
とだけ出力されています。
また、以下のプログラムのように if 文の中の処理は複数行にわたって書くこともできます。
x = int(input()) if x < 10: print("x は 10 より小さい") print(x) print("終了")
if 文のように、ある条件を満たすかどうかで実行する処理を変えることを条件分岐といいます。
また、「条件式が正しい(条件式が当てはまる)」ことを条件式が真、「条件式が正しくない(条件式が当てはまらない)」ことを条件式が偽といいます。
比較演算子
if 文のところで使った条件式 x < 10
は、比較演算子のひとつです。
一般的な比較演算子には以下の 6 つがあります。
演算子 | 意味 |
---|---|
x == y |
x と y は等しい |
x != y |
x と y は等しくない |
x > y |
x は y より大きい |
x < y |
x は y より小さい |
x >= y |
x は y 以上 |
x <= y |
x は y 以下 |
「x
と y
は等しい」は「x == y
」と =
を 2 つ繋げたもので表し、「x
と y
は等しくない」は「x != y
」と表します。
「x
は y
より大きい」「x
は y
より小さい」は、数学での記号と同じく >
, <
で表します。
「x
は y
以上」「x
は y
以下」は、数学では \geqq, \leqq のように =
を下につけますが、Python では =
を右につけることで表します。
次のプログラムは、入力された整数値がどのような条件を満たしているかを出力するプログラムです。
x = int(input()) if x < 10: print("x は 10 より小さい") if x >= 20: print("x は 20 以上") if x == 5: print("x は 5") if x != 100: print("x は 100 ではない") print("終了")
入力 1
5
実行結果 1
x は 10 より小さい x は 5 x は 100 ではない 終了
入力 2
100
実行結果 2
x は 20 以上 終了
論理演算子
条件式の部分にはもっと複雑な条件を書くこともできます。そのためには論理演算子を使います。
論理演算子には以下の 3 つがあります。
演算子 | 意味 | 真になるとき |
---|---|---|
not 条件式 |
条件式 の真偽の反転 |
条件式 が偽 |
条件式1 and 条件式2 |
「条件式1 が真」かつ「条件式2 が真」 |
条件式1 と 条件式2 のどちらも真 |
条件式1 or 条件式2 |
「条件式1 が真」または「条件式2 が真」 |
条件式1 と 条件式2 の少なくとも片方が真 |
x, y = map(int, input().split()) if not x == y: print("x と y は等しくない") if x == 10 and y == 10: print("x と y は 10") if x == 0 or y == 0: print("x か y は 0") print("終了")
入力 1
2 3
実行結果 1
x と y は等しくない 終了
入力 2
10 10
実行結果 2
x と y は 10 終了
入力 3
0 8
実行結果 3
x と y は等しくない x か y は 0 終了
「前の条件が真でないとき」の処理
else 節
else 節は、if 文の後に書くことで、「if 文の条件が偽のとき」に処理を行えるようになります。
書き方は次のとおりです。
if 条件式: 処理1 else: 処理2
次のプログラムは、入力の値が 10 より小さければ「x は 10 より小さい
」と出力し、そうでなければ「x は 10 より小さくない
」と出力します。
x = int(input()) if x < 10: print("x は 10 より小さい") else: print("x は 10 より小さくない")
入力 1
5
実行結果 1
x は 10 より小さい
入力 2
15
実行結果 2
x は 10 より小さくない
elif 節
elif 節は「「前の if 文の条件が偽」かつ「elif 節の条件が真」」のときに処理が行われます。
書き方は次のとおりです。
if 条件式1: 処理1 elif 条件式2: 処理2
処理2が実行されるのは「条件式1が偽 かつ 条件式2が真」のときになります。
また、elif 節のあとに続けて elif 節や else 節を書くこともできます。
次のプログラムはその例です。
x = int(input()) if x < 10: print("x は 10 より小さい") elif x > 20: print("x は 10 より小さくなくて、20 より大きい") elif x == 15: print("x は 10 より小さくなくて、20 より大きくなくて、15 である") else: print("x は 10 より小さくなくて、20 より大きくなくて、15 でもない")
入力 1
5
実行結果 1
x は 10 より小さい
入力 2
30
実行結果 2
x は 10 より小さくなくて、20 より大きい
入力 3
15
実行結果 3
x は 10 より小さくなくて、20 より大きくなくて、15 である
入力 4
13
実行結果 4
x は 10 より小さくなくて、20 より大きくなくて、15 でもない
if 文のネスト
if, elif, else などの節の中にさらに if 文を入れることで、より複雑な条件分岐をすることもできます。
x = int(input()) if x % 2 == 0: if x % 3 == 0: print("x は 2 の倍数でも 3 の倍数でもある") else: print("x は 2 の倍数ではあるが 3 の倍数ではない") else: if x % 3 == 0: print("x は 2 の倍数ではないが 3 の倍数ではある") else: print("x は 2 の倍数でも 3 の倍数でもない")
入力 1
12
実行結果 1
x は 2 の倍数でも 3 の倍数でもある
入力 2
14
実行結果 2
x は 2 の倍数ではあるが 3 の倍数ではない
入力 3
15
実行結果 3
x は 2 の倍数ではないが 3 の倍数ではある
入力 4
17
実行結果 4
x は 2 の倍数でも 3 の倍数でもない
比較演算子の連鎖
次のプログラムは、x
が 0 以上 100 以下かどうかを調べるプログラムです。
x = int(input()) if 0 <= x and x <= 100: print("x は 0 以上 100 以下です") else: print("x は 0 以上 100 以下ではありません")
上のプログラムの 0 <= x and x <= 100
の部分を、Python では次のプログラムのように 0 <= x <= 100
と書くこともできます。
x = int(input()) if 0 <= x <= 100: print("x は 0 以上 100 以下です") else: print("x は 0 以上 100 以下ではありません")
また、a < b < c < d
のように 3 つ以上繋げて書いたり、a < b > c
のように異なる演算子についても繋げて書くことができます(a < b > c
は a < b and b > c
と同じ意味です)。
💡 ちなみに
この書き方は、あまり濫用するとかえってわかりづらくなってしまうので注意が必要です。例えば、a != b != c
と書いたとき、これは a != b and b != c
と同じ意味なので、a
と c
が異なるかどうかを判定していません。
問題
リンク先の問題を解いてください。
Time Limit: 0 msec / Memory Limit: 0 KiB
キーポイント
- 条件式の値は bool 型という型の値である。
- bool 型は
True
(真) とFalse
(偽) の2種類の値のいずれかを取る型である。 - 数値や文字列など色々な値は条件式の値として用いることができる。結果は型ごとにルールが決まっている。
- 数値の場合、
0
でなければTrue
- 文字列の場合、
""
(空文字列) でなければTrue
- 数値の場合、
条件式の結果
1.06節では if x == 0:
のように if 文を用いることで条件分岐を記述できる、ということを扱いました。じつはこの「x == 0
」の部分自体が値を持っています。このことを確認してみましょう:
x = 0 print(x == 0) # 「x が 0 と等しいかどうか」を出力 print(x >= 1) # 「x が 1 以上かどうか」を出力
実行結果
True False
条件式が成り立っている場合の値は "True"、成り立っていない場合は値は "False" と出力されました。これらは、それぞれ日本語でいうところの「真」「偽」に相当します。条件式は、それが成り立っている場合は真、成り立っていない場合は偽、という値を持っている、ということになります。
条件式の結果は値を持つので、これを変数に格納したり、格納した変数同士で演算を行うことも可能です。以下の例では条件式の値を変数 a
, b
に代入し、それらの論理演算を行っています。
x = 5 a = 0 <= x # a に「xが0以上か」の条件式の値を代入 (ここでは True) b = x <= 10 # b に「xが10以下か」の条件式の値を代入 (ここでは True) c = a and b # c に「a かつ b か」の論理演算式の値を代入 print(a,b,c)
実行結果
True True True
bool 型
True と False
先程出力された条件式の値である True
False
は bool型というデータ型の値です。bool型はこれらTrue
(真) もしくは False
(偽) の2種類の値のみを取ります。
bool 型の値は、前節のように条件式を記述する以外にも、True
False
を直接記述することでも表現することができます。
if True: print("条件が成り立っています") if False: print("条件が成り立っています") else: print("条件が成り立っていません")
実行結果
条件が成り立っています 条件が成り立っていません
他の型からの変換
実は if 文の条件式には True
False
以外の値を与えることも可能です:
if 10: print("10 は True 扱い") if not 0: print("0 は False 扱い") if -1: print("-1 は True 扱い")
実行結果
10 は True 扱い 0 は False 扱い -1 は True 扱い
上記のように数値を条件式に与えた場合、「値が0でなければ True
、0 と等しければ False
」というルールで変換が行われます。このような型の変換のことをキャストと呼びます。上記の例だと int
型が bool
型にキャストされた、ということになります。
キャストは数値以外でも行われます。代表的な例を次の表に記載します:
型 | 変換ルール | True となる例 | False となる例 |
---|---|---|---|
数値 (int , float ) |
値が0と等しいときのみ False |
1 , 0.01 , -1e18 |
0 , 0.0 |
文字列 | 空文字列 "" のときのみ False |
"hoge" , "A" , " " |
"" |
配列1 | 配列が空 (長さが 0) のときのみ False |
[1,2,3] , ["foo"] , [0] |
[] |
問題
リンク先の問題を解いてください。
Time Limit: 0 msec / Memory Limit: 0 KiB
キーポイント
- while 文を使うと繰り返し処理ができる
- 条件式が真のとき処理を繰り返す
while 条件式: 処理
- 「N 回処理する」というプログラムを書く場合、「カウンタ変数を 0 からはじめ、カウンタ変数が N より小さいときにループ」という形式で書く
i = 0 # カウンタ変数 while i < N: 処理 i += 1
while 文
while 文を使うと、プログラムの機能の中でも非常に重要な「繰り返し処理」(ループ処理)を行うことができます。
無限ループ
次のプログラムは、「Hello
と出力して改行した後、AtCoder
と出力する処理」を無限に繰り返すプログラムです。
while True: print("Hello") print("AtCoder")
実行結果
Hello AtCoder Hello AtCoder Hello AtCoder Hello AtCoder Hello AtCoder ...(無限に続く)
while 文は次のように書き、条件式が真のとき処理を繰り返し続けます。
while 条件式: 処理
先のプログラムでは条件式の部分に True
と書いているため、無限に処理を繰り返し続けます。
このように、無限に繰り返し続けることを無限ループと言います。
1 ずつカウントする
次のプログラムは、1 から順に整数を出力し続けます。
i = 1 while True: print(i) i += 1 # ループのたびに 1 増やす
実行結果
1 2 3 4 5 6 7 8 ...(無限に 1 ずつ増えていく)
ループ回数の指定
1 ずつカウントするプログラムを「1 から 5 までの数を出力するプログラム」に変える場合、次のようにします。
i = 1 # i が 5 以下の間だけループ while i <= 5: print(i) i += 1
実行結果
1 2 3 4 5
カウンタ変数は 0 から N 未満まで
5 回 Hello
と出力されるプログラムを考えます。
以下では書き方を二つ紹介します。この教材では二つ目の書き方を推奨します。
推奨しない書き方
# i を 1 からはじめる i = 1 # i が 5 以下の間だけループ while i <= 5: print("Hello") i += 1
実行結果
Hello Hello Hello Hello Hello
「N 回処理をする」というプログラムを while 文で書く場合、今までは「i
を 1 からはじめ、N 以下の間ループする」という形式で書いてきました。
i = 1 while i <= N: 処理 i += 1
この形式は一見わかりやすいと感じるかもしれません。
しかし、この書き方はあまり一般的ではなく、次のように書いたほうがより一般的です。
推奨する書き方
# i を 0 からはじめる i = 0 # i が 5 未満の間だけループ while i < 5: print("Hello") i += 1
実行結果
Hello Hello Hello Hello Hello
「N 回処理する」というプログラムを書く場合、次のように「i
を 0 からはじめ、N より小さいときにループする」という形式で書くのがより一般的です。
i = 0 while i < N: 処理 i += 1
最初は分かりづらく感じるかもしれませんが、この形式で書いた方がプログラムをよりシンプルに書けることがあるので、慣れるようにしましょう。
なお、このプログラムの変数 i
のように、「何回目のループか」を管理する変数のことをカウンタ変数と呼ぶことがあります。カウンタ変数には慣例的に i
を使い、i
が使えない場合は j
, k
, l
... と名前をつけていくことが多いです。
応用例
N 人の合計点を求めるプログラムを作ってみましょう。
次のプログラムは「入力の個数 N」と「点数を表す N 個の整数」を入力で受け取り、点数の合計を出力します。
N = int(input()) s = 0 # 合計点を表す変数 i = 0 # カウンタ変数 while i < N: x = int(input()) s += x i += 1 print(s)
入力
3 1 10 100
実行結果
111
合計点を表す変数 s
を用意して、ループするたびに入力を変数 x
に受け取り s
に足しています。
このように、繰り返し処理を使えば様々な処理が行えるようになります。
細かい話
細かい話なので、飛ばして問題を解いても良いです。
2 ずつ増やす
今までは i
を 1 ずつだけ増やしてきましたが、2 ずつ増やすこともできます。
i = 0 while i < 10: print(i) i += 2
実行結果
0 2 4 6 8
以下のように書くことで i
を 2 ずつ増やしています。
i += 2
同様にして、より多く飛ばしてループすることもできます。
逆順ループ
5 から 0 までの数を出力したい場合は以下のようにします。
i = 5 while i >= 0: print(i) i -= 1
実行結果
5 4 3 2 1 0
以下のように書くことで i
を 1 ずつ減らしています。
i -= 1
無限ループをコードテストで実行した場合
AtCoder のコードテストでは、実行時間が長すぎるとプログラムが中断されます。また、出力が長すぎる場合も途中から省略されます。
そのため、はじめに紹介した「Hello
と出力して改行した後、AtCoder
と出力する処理」を無限に繰り返すプログラムをコードテストで実行しても無限には出力されず、一例としては次のように表示されます。
実行結果
Hello AtCoder (中略) Hello AtCoder H
終了コード
9
無限ループが発生した場合、終了コードは実行時間が長すぎることを表す 9
となります。
問題
リンク先の問題を解いてください。
Time Limit: 0 msec / Memory Limit: 0 KiB
キーポイント
- for 文は繰り返し処理でよくあるパターンを while 文より短く書くための構文
- 列の中身が順に変数に代入される
for 変数 in 列: 処理
- N 回の繰り返し処理は以下の for 文で書くのが一般的
for i in range(N): 処理
break
を使うとループを途中で抜けることができるcontinue
を使うと後の処理を飛ばして次のループへ進むことができる
for 文
for 文は列の中身を順に処理するための構文です。
for 文を使うことで「N 回処理する」というような繰り返し処理でよくあるパターンを while 文より短く書くことができます。
3 回繰り返すプログラムを while 文と for 文で書くと次のようになります。
j = 0 while j < 3: print("Hello while:", j) j += 1 for i in range(3): print("Hello for:", i)
実行結果
Hello while: 0 Hello while: 1 Hello while: 2 Hello for: 0 Hello for: 1 Hello for: 2
for 文は次のように書きます。
for 変数 in 列: 処理
列として代表的なものにはリストや文字列が存在しますが、詳しくは後の節で紹介します。
N 回の繰り返し処理
「N 回処理をする」というような繰り返し処理をしたいときは、range()
を使うと楽です。range(a, b)
と書くことで、(a, a+1, a+2, \dots , b-1) という数列が生成されます。
for i in range(0, 5): print(i)
実行結果
0 1 2 3 4
range(a, b)
の a
は 0 のときに省略することができ、range(b)
と書くことができます。よって、次の二つの書き方は同じ意味になります。
for i in range(0, 5): print(i) for i in range(5): print(i)
実行結果
0 1 2 3 4 0 1 2 3 4
つまり、「N 回の繰り返し処理」を書きたいときは次のように書けば良いです。
for i in range(N): 処理
break と continue
while 文と for 文を制御する命令として、break と continue があります。
break
break はループを途中で抜けるための命令です。
# break がなければこのループは i == 4 まで繰り返す for i in range(5): if i == 3: print("ぬける") break # i == 3 の時点でループから抜ける print(i) print("終了")
実行結果
0 1 2 ぬける 終了
上のプログラムでは、if 文で i == 3
が真になったとき break 文を実行することで for ループを抜け、終了
と表示しています。
continue
continue は後の処理をとばして次のループへ進むための命令です。
for i in range(5): if i == 3: print("とばす") continue # i == 3 のとき これより後の処理を飛ばす print(i) print("終了")
実行結果
0 1 2 とばす 4 終了
上のプログラムでは、if 文で i == 3
が真になったとき continue 文を実行することで continue
より下の部分を飛ばし、次のループに進みます。
細かい話
細かい話なので、飛ばして問題を解いても良いです。
2 ずつ増やす
上では range(a, b)
という書き方のみ紹介しましたが、実は range(a, b, c)
という書き方もできこれによりいくつずつ増やすかを決めることができます(c
を省略したときのデフォルトは 1 なので、これまでの書き方では全て 1 ずつ増えていました)。
よって、次のように書くことで 2 ずつ増やすことができます。
for i in range(0, 10, 2): print(i)
実行結果
0 2 4 6 8
同様にして、より多く飛ばしてループすることもできます。
なお、いくつずつ増やすかを決めるときは a
がたとえ 0 だとしても省略できないことに注意してください。つまり、上のコードで range(10, 2)
と書くと異なる挙動になってしまいます。
逆順ループ
5 から 0 までの数を出力したい場合は以下のようにします。
for i in range(5, -1, -1): print(i)
実行結果
5 4 3 2 1 0
0 まで出力したい場合に二つ目の引数を -1 にする必要があることに注意してください。range(5)
としたときに(5 までではなく)4 までしか出力されなかったのと同様に、0 まで出力したい場合はひとつ次の -1 を指定する必要があります。
また、もちろん三つ目の引数に -2 などを指定することでより多く飛ばして逆順ループすることもできます。
より一般的に、range(N)
を逆順にするときは range(N - 1, -1, -1)
と書くことになります。
N = 5 print("昇順") for i in range(N): print(i) print("降順") for i in range(N - 1, -1, -1): print(i)
実行結果
昇順 0 1 2 3 4 降順 4 3 2 1 0
for-else, while-else
Python では for 文と while 文に else 節を書くことができます。
これは、ループが break 文で終了した場合は実行されず、そうでないとき(ループが最後まで回ったとき)に実行されます。
break しない場合
for i in range(5): print(i) else: print("ループが最後まで回りました。") print("終了")
実行結果
0 1 2 3 4 ループが最後まで回りました。 終了
break する場合
for i in range(5): if i == 3: print("ぬける") break # i == 3 の時点でループから抜ける print(i) else: print("ループが最後まで回りました。") print("終了")
実行結果
0 1 2 ぬける 終了
問題
リンク先の問題を解いてください。
Time Limit: 0 msec / Memory Limit: 0 KiB
キーポイント
- リスト は様々なデータの列を扱うことができる
[要素0, 要素1, 要素2, ...]
でリストを作ることができる[0] * N
で N 個の 0 からなるリストを作ることができるa[i]
でリスト a の i 番目の要素を取得できるlist(map(int, input().split()))
で 1 行の入力から整数のリストを作ることができる- リストと for 文を組み合わせると、大量のデータを扱うプログラムを簡単に書ける
リスト
リスト(配列、list 型) はデータの列を扱うことができる非常に重要な機能です。例えば、以下のようなコードを書くことができます。
# リスト [3, 1, 4, 1, 5] を作り, a に代入する a = [3, 1, 4, 1, 5] # a の 0 番目の要素である 3 を出力 print(a[0]) # a の 1 番目の要素である 1 を出力 print(a[1]) # a の 2 番目の要素 (4) を 7 に変更 a[2] = 7 # a の要素を順に出力 for x in a: print(x)
出力
3 1 3 1 7 1 5
この章では、リストの使い方について説明します。
リストを作る
[]
で空のリストを作ることができます。[要素0, 要素1, 要素2, ...]
でリストを作ることができます。list( for文に入れられるもの )
でリストを作ることができます。- 例えば、
for i in range(5):
と書くと i に 0, 1, 2, 3, 4 が順番に入るので、list(range(5))
と書くとリスト[0, 1, 2, 3, 4]
を作ることができます。
- 例えば、
- 2 章で紹介する リスト内包表記 を使ってもリストを作ることができます。
# 空のリストを作り、a に代入する a = [] # リスト [3, 1, 4, 1, 5] を作り、a に代入する a = [3, 1, 4, 1, 5] # いろいろな型を持つデータを 1 つのリストに入れることができる a = ["Hello", "AtCoder", 123, 4.5, []] # リスト [0, 1, 2, 3, 4] を作る a = list(range(5))
リストの長さ
- リスト a に対し、
len(a)
で a の長さ(要素数)を取得できます。
リストの i 番目の要素
- リスト a と整数 i に対し、
a[i]
で a の i 番目の要素を取得できます。- この i のこと(リストの何番目の要素かを表す番号)を 添字(そえじ) と言います。
- リストの添字は 0 から始まることに注意しましょう。 先頭の要素は
a[0]
で、末尾の要素はa[len(a)-1]
で取得することができます。
a[i] = x
で a の i 番目の要素を x に変更できます。
# リスト [3, 1, 4, 1, 5] を作り、a に代入する a = [3, 1, 4, 1, 5] # a の長さ 5 を出力する print(len(a)) # a の 2 番目の要素 (4) を 7 に変更する a[2] = 7 # range(len(a)) と書けば a の添字 (0, 1, 2, 3, 4) を順に列挙できる for i in range(len(a)): # a[i] で a の i 番目の要素を取得する print(i, ":", a[i])
出力
5 0 : 3 1 : 1 2 : 7 3 : 1 4 : 5
for 文でリストの要素を列挙する
- リスト a に対し、
for x in a:
で a の要素を順に x に代入して繰り返し処理を行うことができます。
a = [3, 1, 4, 1, 5] # a の要素を順に x に代入し、繰り返し処理を行う for x in a: # 5 回実行される print(x)
出力
3 1 4 1 5
入力からリストを作る
- 文字列 s に対し、
s.split()
で s を空白で区切って文字列のリストを作ることができます。 - 文字列のリスト a に対し、
list(map(int, a))
で a の各要素を整数に変換したリストを作ることができます。 list(map(int, input().split()))
で、入力から 1 行読み取り、空白で区切って整数のリストを作ることができます。
# 入力から 1 行読み取り、空白で区切って整数のリストを作る a = list(map(int, input().split())) print(a)
入力
886 551 37 424 531
出力
[886, 551, 37, 424, 531]
リストを出力する
- リスト a に対し、
print(*a)
で a の要素を空白区切りで出力できます。
a = [9, 9, 7, 3] # a の要素を空白区切りで出力する print(*a) # a の要素を改行区切りで出力する for x in a: print(x)
出力
9 9 7 3 9 9 7 3
リストの使い所
リストと for 文を組み合わせると、以下のように大量のデータを扱うプログラムを書くことができます。
例題
N 人の生徒が数学のテストと英語のテストを受けました。テストはそれぞれ 100 点満点で、生徒 i\ (1 \le i \le N) の数学の点数は M_i 、英語の点数は E_i でした。それぞれの生徒について、数学のテストと英語のテストの合計点を求めてください。
制約
- 入力される値はすべて整数
- 1 \le N \le 1000
- 0 \le M_i \le 100\ (1 \le i \le N)
- 0 \le E_i \le 100\ (1 \le i \le N)
入力
入力は以下の形式で与えられる。
N M_1 M_2 \cdots M_N E_1 E_2 \cdots E_N
出力
S_i\ (1 \le i \le N) を生徒 i の数学のテストと英語のテストの合計点とする。
以下の形式で出力せよ。
S_1 S_2 \vdots S_N
入力例
3 20 100 30 100 5 40
出力例
120 105 70
解答例
N \le 3 のような N が非常に小さいという制約がついていれば、N で場合分けすることによってリストを使わずに解くことも可能です。しかし、N が大きくなるとこれは非現実的になってしまいます。
リストと for 文を用いれば、N の大きさに関わらず簡潔に処理を書くことができます。
# 生徒の人数 N を入力 N = int(input()) # 数学の点数 M を入力 M = list(map(int, input().split())) # 英語の点数 E を入力 E = list(map(int, input().split())) # それぞれの生徒について処理する for i in range(N): # 生徒 i の合計点 M[i] + E[i] を出力する print(M[i] + E[i])
リストのさらなる機能
負の添字
- リスト a と負の整数 i に対し、
a[i]
で a の 後ろから -i-1 番目の要素を取得することができます。末尾の要素はa[-1]
で、先頭の要素はa[-len(a)]
で取得することができます。
範囲外アクセス
- 存在しない要素を取得しようとすると、RE が発生します。
a = [3, 1, 4, 1, 5] # 負の添字で後ろから順番に要素を取得 print(a[-1]) print(a[-2]) print(a[-3]) # a の 5 番目の要素は存在しないので、実行時エラーが発生する print(a[5])
出力
5 1 4
エラー出力
Traceback (most recent call last): File "/judge/Main.py", line 8, in <module> print(a[5]) ^^^^ IndexError: list index out of range
終了コード
256
エラー出力を見ると、8 行目の a[5]
の部分で、IndexError: list index out of range
(リストの添字の範囲外) というエラーが起きていることがわかります。
リストをつなげる
- リスト a とリスト b に対し、
a + b
で a と b をつなげたリストを作ることができます。 - リスト a と整数 n に対し、
a * n
で a を n 回繰り返したリストを作ることができます。
# リスト [1, 6] とリスト [1, 8] をつなげて [1, 6, 1, 8] を作り、a に代入する a = [1, 6] + [1, 8] # [1, 6, 1, 8] に [0, 3, 3] をつなげて [1, 6, 1, 8, 0, 3, 3] とする a += [0, 3, 3] # リスト [0, 1, 2] を 3 回繰り返したリスト [0, 1, 2, 0, 1, 2, 0, 1, 2] を作り、a に代入する a = [0, 1, 2] * 3
リストの末尾に要素を追加・削除する
- リスト a と値 x に対し、
a.append(x)
で a の末尾に要素 x を追加できます。 - リスト a に対し、
a.pop()
で a の末尾の要素を取得しながら削除できます。
# 空のリストを作り、a に代入する a = [] # a の末尾に 1 を追加する。a は [1] になる。 a.append(1) # a の末尾に 2 を追加する。a は [1, 2] になる。 a.append(2) # a の末尾に 3 を追加する。a は [1, 2, 3] になる。 a.append(3) # a の末尾の要素 3 を出力し、削除する。a は [1, 2] になる。 print(a.pop()) # a の末尾の要素 2 を出力し、削除する。a は [1] になる。 print(a.pop()) # a の末尾の要素 1 を出力し、削除する。a は [] になる。 print(a.pop())
出力
3 2 1
リストの好きな位置に要素を追加・削除する
- リスト a と整数 i と値 x に対し、
a.insert(i, x)
で a の i 番目の位置 (a[i-1]
とa[i]
の間) に x を挿入することができます。 - リスト a と整数 i に対し、
a.pop(i)
で a の i 番目の要素を取得しながら削除することができます。
リストの途中への挿入・削除は、それ以降の要素をずらす必要があるため、末尾への挿入・削除と比べて時間がかかります。
リストの中に x があるか調べる
- リスト a と値 x に対し、
x in a
で x が a の中に 1 個以上存在するかを判定することができます。not (x in a)
はx not in a
と書くこともできます。
- リスト a と値 x に対し、
a.count(x)
で x が a の中に何個存在するかを取得することができます。 - リスト a と値 x に対し、
a.index(x)
で a の中に x が出現する最初の位置を取得することができます。
# x が 1, 2, 3 のいずれかであるかを判定する x = 2 print(x in [1, 2, 3]) a = [3, 1, 4, 1, 5] # a の中に存在する 1 の数 (2 個) を数える print(a.count(1)) # a の中に存在する最初の 1 の位置 (a[1]) を調べる print(a.index(1))
出力
True 2 1
リストをソートする
- リスト a に対し、
a.sort()
で a の要素を昇順に並べ替えることができます。
リストを反転する
- リスト a に対し、
a.reverse()
で a の要素を逆順に並べ替えることができます。
a = [3, 1, 4, 1, 5] # a を昇順に並び替える。a は [1, 1, 3, 4, 5] になる。 a.sort() print(a) # a を反転する。a は [5, 4, 3, 1, 1] になる。 a.reverse() print(a)
出力
[1, 1, 3, 4, 5] [5, 4, 3, 1, 1]
リストを複製する
リスト a に対し b = a
とすると、リストは複製されず、a の指すリストと b の指すリストは同一のものになります。
# リスト [3, 1, 4, 1, 5] を作り、a に代入する a = [3, 1, 4, 1, 5] # a が指しているリストを b に代入する b = a # a の指すリストと b の指すリストは同一のため、a を変更すると b も変わっている a[2] = 7 print(b) # b を変更すると a も変わっている b.append(9) print(a)
出力
[3, 1, 7, 1, 5] [3, 1, 7, 1, 5, 9]
これを回避するには、b = a.copy()
と書いて、リストを複製する必要があります。
# リスト [3, 1, 4, 1, 5] を作り、a に代入する a = [3, 1, 4, 1, 5] # a が指しているリストを複製して b に代入する b = a.copy() # a の指すリストと b の指すリストは別物のため、a を変更しても b は変わらない a[2] = 7 print(b)
出力
[3, 1, 4, 1, 5]
リストの一部分を複製する(スライス)
- リスト a と整数 l,r\ (0 ≤ l ≤ r ≤ \text{len}(a)) に対し、
a[l:r]
で a[l] から a[r-1] までの r-l 要素からなるリストを作ることができます。
a = [3, 1, 4, 1, 5] # a[1] から a[3] までの 3 要素からなるリストを作る print(a[1:4]) # a[0] から a[4] までの 5 要素からなるリストを作る print(a[0:5])
出力
[1, 4, 1] [3, 1, 4, 1, 5]
- リスト a と正整数 l,r\ (0 ≤ l ≤ r ≤ \text{len}(a))、正整数 \mathit{step} に対し、
a[l:r:step]
でa[l:r]
の範囲から \mathit{step} 要素ごとに取り出したリストを作ることができます。- より正確には、d = \bigl\lceil\frac{r-l}{\mathit{step}}\bigr\rceil とすると、長さ d のリスト
[a[l+step*0], a[l+step*1], ..., a[l+step*(d-1)]]
が作られます。 - l を省略すると l = 0 扱いに、r を省略すると r = \text{len}(a) 扱いに、\mathit{step} を省略すると \mathit{step} = 1 扱いになります。特に
a[:]
と書くと、リストa
を複製することができます。
- より正確には、d = \bigl\lceil\frac{r-l}{\mathit{step}}\bigr\rceil とすると、長さ d のリスト
a = [3, 1, 4, 1, 5, 9] # a[0:6] を作る (リストを複製する) print(a[:]) # a[0:6] から 2 要素ごとに取り出したリストを作る print(a[::2]) # a[1:6] から 2 要素ごとに取り出したリストを作る print(a[1::2]) # a[1:5] から 3 要素ごとに取り出したリストを作る print(a[1:5:3])
出力
[3, 1, 4, 1, 5, 9] [3, 4, 5] [1, 1, 9] [1, 5]
リストを比較する
- リスト a, b に対し、
a == b
/a != b
でリストの全要素が一致しているかを判定することができます。 - リスト a, b に対し、
a < b
/a <= b
/a > b
/a >= b
で a と b を辞書式順序で比較できます。
公式ドキュメント
より詳しい情報を知りたい場合は、以下の公式ドキュメントを参照してください。
https://docs.python.org/ja/3/library/stdtypes.html#list
問題
リンク先の問題を解いてください。
Time Limit: 0 msec / Memory Limit: 0 KiB
キーポイント
"
(二重引用符)や'
(一重引用符)で囲んで文字列を作るlen(s)
で文字列 s の長さを得られるs[i]
で文字列 s の i 文字目を得られるs + t
で文字列 s, t を連結できる- 文字列は変更不可能。一部を変更したいときは、文字のリストを作る
文字列
Hello
や ABCDEFG
や あいうえお
のようなテキスト(文字の並び)のことを 文字列 と言います。Python では、str 型(文字列型) を使って文字列を扱います。例えば、以下のようなプログラムを書くことができます。
# 文字列 "ABCDEFG" を作り a に代入 a = "ABCDEFG" # 文字列 a の 0 番目の文字 "A" を出力 print(a[0]) # 入力から 1 行読み取って文字列を作り b に代入 b = input() # 文字列 a, 空白, 文字列 b をつなげた文字列を作り出力 print(a + " " + b)
入力
あいうえお
出力
A ABCDEFG あいうえお
本章では、この文字列の使い方について説明します。
文字列を作る
"
(二重引用符)や'
(一重引用符)で囲んだ部分が文字列として扱われます。どちらを使っても違いはありません。
# 文字列 "Hello" を作り a に代入 a = "Hello" # 文字列 "AtCoder" を作り b に代入 b = 'AtCoder'
エスケープシーケンス
通常、以下のように文字列の中に改行をそのまま書くことはできません。
# 文字列の中に改行を入れたい s = "Hello AtCoder"
文法エラー
File "Main.py", line 2 s = "Hello ^^^^^^ SyntaxError: unterminated string literal (detected at line 2) (訳)文法エラー: 終端のない文字列リテラル (2 行目で検出)
このような文法上意味のある文字を文字列に入れたい場合、\
(バックスラッシュ)から始まる エスケープシーケンス を利用します。主なエスケープシーケンスは以下の通りです。
エスケープシーケンス | 意味 |
---|---|
\n |
改行文字 |
\" |
" (二重引用符) |
\' |
' (一重引用符) |
\\ |
\ (バックスラッシュ) |
\t |
水平タブ |
# 文字列の中に改行文字を入れるには \n と書く print("Hello\nAtCoder") # 二重引用符で囲まれた文字列の中で二重引用符を入れるには \" と書く # 文字列の中にバックスラッシュを入れるには \\ と書く print("\"Hello\\nAtCoder\"")
出力
Hello AtCoder "Hello\nAtCoder"
文字列の長さ
- 文字列 s に対し、
len(s)
で s の長さ(文字数)を取得できます。
for 文で文字列の中の文字を列挙する
- 文字列 s に対し、
for x in s:
で s の中の文字を順に x に代入して繰り返し処理を行うことができます。
s = "AtCoder" for x in s: print(x)
出力
A t C o d e r
入力から文字列を作る
input()
で入力から 1 行読み取って文字列を作ることができます。
文字列を出力する
- 文字列 s に対し、
print(s)
で s を出力することができます。
例
# 入力から 1 行読み取り、そのまま出力する print(input())
入力
Hello, AtCoder!
出力
Hello, AtCoder!
文字列をつなげる
- 文字列 s, t に対し、
s + t
で s, t をこの順に連結した文字列を作ることができます。 - 文字列 s と非負整数 n に対し、
s * n
で s を n 回繰り返した文字列を作ることができます。
# "ABC" と "123" をつなげた文字列 "ABC123" を出力する print("ABC" + "123") # "ABC" を 3 回繰り返した文字列 "ABCABCABC" を出力する print("ABC" * 3)
出力
ABC123 ABCABCABC
文字列を整数に変換する
- 文字列 s に対し、
int(s)
で s を整数に変換することができます。
整数を文字列に変換する
- 整数 x に対し、
str(x)
で x を文字列に変換することができます。
# 999999999999999999999999999999 + 1 を出力 print(int("9" * 30) + 1) # 3 ** 1000 の桁数を出力 print(len(str(3 ** 1000)))
出力
1000000000000000000000000000000 478
整数・文字列変換における桁数制限
整数から文字列への変換、文字列から整数への変換において、変換に時間がかかることを防ぐため、4300 桁よりも大きな数は変換できないようになっています。整数を出力するときにも整数から文字列への変換が行われるので、この制限が適用されます。
print(10 ** 4300)
エラー出力
Traceback (most recent call last): File "/judge/Main.py", line 1, in <module> print(10 ** 4300) ValueError: Exceeds the limit (4300) for integer string conversion; use sys.set_int_max_str_digits() to increase the limit (訳) 値エラー: 整数・文字列変換における上限 (4300) を超えています; 上限を増やすには sys.set_int_max_str_digits() を使ってください
sys.set_int_max_str_digits(0)
を実行することで、この制限を取り除くことができます。
import sys sys.set_int_max_str_digits(0) print(10 ** 4300)
出力
10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
文字列を空白文字で区切る
- 文字列 s に対し、
s.split()
で s を空白文字で区切って文字列のリストを作ることができます。
文字列のリストを連結する
- 文字列 s と文字列のリスト a に対し、
s.join(a)
で s を区切り文字列として a の各要素を連結することができます。
# "1 23 456" を空白文字で区切り、リスト ["1", "23", "456"] を作る a = "1 23 456".split() # ["1", "23", "456"] を文字列 " + " で結合してできる文字列 "1 + 23 + 456" を出力する print(" + ".join(a))
出力
1 + 23 + 456
文字列の i 番目の文字
- 文字列 s と整数 i に対し、
s[i]
で s の i 番目の文字を取得できます。 - リストのときと同様に、負の添字やスライスを使うことができます。
# 文字列 "ABCDE" を作り s に代入する s = "ABCDE" # s の 0 番目の文字 "A" を出力する print(s[0]) # s の最後の文字 "E" を出力する print(s[-1]) # s の 1 番目から 3 番目までの文字列 "BCD" を出力する print(s[1:4]) # s を逆順にした文字列 "EDCBA" を出力する print(s[::-1])
出力
A E BCD EDCBA
部分文字列に x があるか調べる
文字列 s の部分文字列とは、s の連続する一部分のことです。部分文字列とは?
例えば、空文字列、A
、BC
、ABC
は ABC
の部分文字列ですが、D
や AC
は ABC
の部分文字列ではありません。
- 文字列 s, t に対し、
t in s
で s の部分文字列に t が含まれるかどうか判定することができます。 - 文字列 s, t に対し、
s.index(t)
で s に現れる最初の部分文字列 t の位置を求めることができます。 - 文字列 s, t に対し、
s.count(t)
で s に部分文字列 t が重ならずに何回出現するかを求めることができます。
# 文字列 "ABABABA" を作り s に代入する s = "ABABABA" # "ABABABA" の部分文字列に "BAB" があるか判定する print("BAB" in s) # "ABABABA" に現れる最初の部分文字列 "BAB" の位置を求める # s[1:4] が最初の "BAB" なので、1 を出力する print(s.index("BAB")) # "ABABABA" に重ならずに現れる部分文字列 "BAB" の個数を求める # s[1:4] と s[3:6] が "BAB" であるが、重ならずに取れるのは 1 個までなので、1 を出力する print(s.count("BAB")) # "AAAAAAAAAA" (A が 10 個) から重ならずに取れる "AAA" は 3 個まで print("AAAAAAAAAA".count("AAA"))
出力
True 1 1 3
部分文字列を置換する
- 文字列 s, t, u に対し、
s.replace(t, u)
で s に重ならずに出現する部分文字列 t をすべて u に置き換えた文字列を取得できます。
# "1 + 2 + 3" に出現する "+" を "**" に置き換えた文字列 "1 ** 2 ** 3" を出力する print("1 + 2 + 3".replace("+", "**")) # "AAAAA" に出現する "AA" を "X" に置き換えた文字列 "XXA" を出力する print("AAAAA".replace("AA", "X"))
出力
1 ** 2 ** 3 XXA
文字列を比較する
- 文字列 s, t に対し、
s == t
/s != t
で s と t が同じ文字列であるかを判定できます。 - 文字列 s, t に対し、
s < t
/s <= t
/s > t
/s >= t
で s と t を辞書式順序で比較できます。
大文字・小文字かどうか判定する
- 文字列 s に対し、
s.isupper()
で s の文字がすべて大文字かどうか判定することができます。- より正確には、
s.isupper()
は s に大文字が含まれていて、小文字が含まれないときTrue
を返します。
- より正確には、
- 文字列 s に対し、
s.islower()
で s の文字がすべて小文字かどうか判定することができます。- より正確には、
s.islower()
は s に小文字が含まれていて、大文字が含まれないときTrue
を返します。
- より正確には、
# "ATCODER" はすべて大文字からなるので、"ATCODER".isupper() は True print("ATCODER".isupper()) # "atcoder" はすべて小文字からなるので、"atcoder".islower() は True print("atcoder".islower()) # 空文字列には小文字が含まれないので、"".islower() は False print("".islower())
出力
True True False
数字かどうか判定する
- 文字列 s に対し、
s.isdigit()
で s が空でなく、s の文字がすべて数字かどうか判定することができます。
# "0123456789" は数字のみからなるので、"0123456789".isdigit() は True print("0123456789".isdigit()) # "ABC123" には数字ではない文字が含まれるので、"ABC123".isdigit() は False print("ABC123".isdigit()) # "".isdigit() は False print("".isdigit())
出力
True False False
大文字・小文字に変換する
- 文字列 s に対し、
s.upper()
で s の小文字をすべて大文字に変換した文字列を取得できます。 - 文字列 s に対し、
s.lower()
で s の大文字をすべて小文字に変換した文字列を取得できます。
# "AtCoder" の小文字をすべて大文字に変換した文字列 "ATCODER" を出力 print("AtCoder".upper()) # "AtCoder" の大文字をすべて小文字に変換した文字列 "atcoder" を出力 print("AtCoder".lower())
出力
ATCODER atcoder
文字列の一部を変更する
Python の文字列は 一度作ると変更することができません。 文字列の一部を変更したい場合は、一度文字のリストに変換する必要があります。
# 文字列 "AtCoder" を作り s に代入する s = "AtCoder" # s は for 文に入れられるものであるから、list(s) で # リスト ["A", "t", "C", "o", "d", "e", "r"] を作れる a = list(s) # a の 0 番目の要素を "M" に変更する a[0] = "M" # a の要素をつなげて出力する print("".join(a))
出力
MtCoder
文字
Python では、1 つの文字は長さ 1 の文字列として str 型で表現されます。 したがって、文字列から取り出した「文字」に対しても文字列に対するさまざまな機能を使うことができます。
# 文字列 "0123456789" の 0 番目の文字 "0" を c に代入 c = "0123456789"[0] # c は長さ 1 の文字列である print(len(c)) # c.isdigit() で "0" が数字であるか判定する print(c.isdigit()) # int(c) で整数に変換できる print(int(c))
出力
1 True 0
文字をコードポイントに変換する
- 文字 c に対し、
ord(c)
で c の Unicode コードポイントを取得できます。
コードポイントを文字に変換する
- 整数 x に対し、
chr(x)
で Unicode コードポイントが x である文字を取得できます。
Unicode とは
様々な文字に番号を割り当て、文字を番号で管理できるようにしたものを 文字コード と言います。文字コードにおいて、ある文字に割り当てられた番号を コードポイント と言います。
Unicode とは、世界中のあらゆる文字に番号を割り当てた文字コードの規格で、Python の内部でも用いられています。
# 文字 "0" に割り当てられた Unicode コードポイントは 48 print(ord("0")) # 文字 "a" に割り当てられた Unicode コードポイントは 97 print(ord("a")) # 文字 "あ" に割り当てられた Unicode コードポイントは 12354 print(ord("あ")) # Unicode コードポイントが 12354 である文字 "あ" を出力する print(chr(12354))
出力
48 97 12354 あ
競技プログラミングにおいては、どのような環境でも扱うことのできる ASCII 印字可能文字 の部分しか基本的に使われません。文字コードについては、以下のことを覚えれば十分でしょう。
0
,1
,2
, ...,9
のコードポイントが連続している。(48 – 57)A
,B
,C
, ...,Z
のコードポイントが連続している。(65 – 90)a
,b
,c
, ...,z
のコードポイントが連続している。(97 – 122)
したがって、以下のようなコードを書くことができます。
- 英大文字 c に対して、
ord(c) - ord("A")
と書けば、c が何番目のアルファベットであるかがわかる。 - 逆に、i 番目のアルファベットを得るには、
chr(ord("A") + i)
と書く。 - 文字 c に対して,
ord("A") <= ord(c) <= ord("Z")
と書けば、c が英大文字であるかどうか判定できる。
公式ドキュメント
より詳しい情報を知りたい場合は、以下の公式ドキュメントを参照してください。
https://docs.python.org/ja/3/library/stdtypes.html#text-sequence-type-str
問題
リンク先の問題を解いてください。
Time Limit: 0 msec / Memory Limit: 0 KiB
キーポイント
- 関数 は与えられた引数に対して処理を行い返り値を返す機能である
- Python が標準で提供している組み込み関数は準備なしに使うことができる
- 組み込み関数には数値の計算やリストの集計など、様々な便利機能を持つものが用意されている
関数
これまでの節で、標準入力を得るための input
やリストの長さを得るための len
などの機能を用いてきました。
これらの機能はより正確には「関数」と呼ばれるものになります。関数とは、与えられた値(引数)に対してある定められた処理を行い、処理の結果の値(返り値)を返す道具です。中には引数が無い関数、返り値が無い関数、複数の返り値を返す関数も存在します。
何度も行う処理を関数としてまとめることで、色々な値に対する処理の結果を計算したいときにプログラムの記述量を大きく減らすことができます。そのため、大半のプログラミング言語において関数は必須の機能となっています。
用語の確認のため、これまでに出てきたいくつかの関数の引数・処理内容・返り値を整理します。
関数 | 引数 | 処理内容 | 返り値 |
---|---|---|---|
len |
リストや文字列 | 長さを取得する | 長さ (整数値) |
input |
無し 1 | 標準入力から1行取得する | 標準入力の1行 (文字列) |
print |
文字列, 整数 など | 標準出力に出力する | 無し2 |
divmod |
数値2つ | 剰余を計算する | 1つめの数値を2つめの数値で割った商と余り |
Python 標準の関数
len
や input
は関数の中でも特別なもので、Python が標準で提供している組み込み機能、とりわけ組み込み関数と呼ばれるものになります。Python が標準で提供しているため、ユーザは特に準備なく使用することができます。
これまでの節で出てきたものを含め、Python では全部で71種類の組み込み関数が用意されています3。
以下ではその中でも競技プログラミングにおいて比較的利用されることの多いものを紹介します。
abs
数値の絶対値を計算して返します。
a = -3 b = 5 a_abs = abs(a) b_abs = abs(b) print(a_abs, b_abs)
実行結果
3 5
pow
pow(a,b)
で a
の b
乗の値を計算します。
pow(a,b,mod)
で a
の b
乗を mod
で割った余りを計算します。
a = 3 b = 4 mod = 10 res = pow(a, b) res2 = pow(a, b, mod) print(res, res2)
実行結果
81 1
なお、べき乗は 1.3 節で扱った **
演算子でも計算できますが、組み込み関数 pow
を用いたほうが高速に計算できます。
また、余りは pow(a,b) % mod
のように後から計算しても同じ結果になりますが、pow(a,b)
の値が非常に大きくなる場合は pow(a,b,mod)
のほうが高速に計算できます。
競技プログラミングにおいては pow
関数を使うことが必須であるケースが多いため覚えておきましょう。
min
max
与えられた2つ以上の数値の中の最小値, 最大値を計算して返します。
min_val = min(1, 3, -5, 2) max_val = max(1, 3, -5, 2) print(min_val, max_val)
実行結果
-5 3
数値をリストとして与えることも可能です。
l = [1, 3, -5, 2] min_val = min(l) max_val = max(l) print(min_val, max_val)
実行結果
-5 3
sum
リストに含まれる値の合計を計算して返します。
l = [1, 3, -5, 2] sum_val = sum(l) print(sum_val)
実行結果
1
sorted
リストをソートし、新たなリストを返します。
l = [1, 3, -5, 2] new_l = sorted(l) print("元のリスト:", l) print("新たなリスト:", new_l)
実行結果
元のリスト: [1, 3, -5, 2] 新たなリスト: [-5, 1, 2, 3]
1.10 節で扱った l.sort()
と異なり、元のリストはソートされていない点に注意してください。
all
any
all
は与えられたリストの要素すべてが条件式として真であるかを判定します。
any
は与えられたリストの要素の中に条件式として真であるものが1つ以上存在するかを判定します。
l = [1, 3, -5, 2] res = all([v>0 for v in l]) # l に含まれる値が全て正か? res2 = any([v%2==0 for v in l]) # l に偶数が含まれるか? print(res, res2)
実行結果
False True
なお、空のリストに対して all
は True
を、any
は False
を返します。
print(all([]), any([]))
実行結果
True False
enumerate
for i,v in enumerate(リスト):
のように書くことでリストのインデックスと値を同時に取得することができます。
l = [1, 3, -5, 2] for i,v in enumerate(l): print(i, "番目の要素は", v, "です")
実行結果
0 番目の要素は 1 です 1 番目の要素は 3 です 2 番目の要素は -5 です 3 番目の要素は 2 です
次のように書いても同一の結果になりますが、enumerate
を使うほうが単純に記述することができます。
l = [1, 3, -5, 2] for i in range(len(l)): v = l[i] print(i, "番目の要素は", v, "です")
問題
リンク先の問題を解いてください。
Time Limit: 0 msec / Memory Limit: 0 KiB
キーポイント
- 関数を定義するためには
def 関数名(パラメータ):
という記法を用いる - 関数の戻り値を返すには
return
文を用いる - 関数外の変数を更新するには
global
文を用いる
関数を自作する
前項では、今まで扱ってきた input
や len
が関数であったことや、これ以外にも色々な関数があることを学びました。この関数は予め用意されたものだけではなく、自分で動作を定めた関数を作成することも可能です。このように関数を作成することを、「関数を定義する」と呼びます。このように関数を定義するメリットの一つとして、複数の場所で行われる同じ処理をまとめることができるという点があります。
最も単純な関数定義
まず、最も単純な関数定義から見ていきます。以下は、1, 2, 3を 3 行で出力するという処理をする関数の定義です。
def print_numbers(): print(1) print(2) print(3)
def
は定義の英語であるdefine
から来ているキーワードで、関数の定義をすることを表します。それに続く部分(今回の場合はprint_numbers
)は関数の名前を表し、変数名と同様に自由な名前が使用できます。その後の ():
の部分は後ほど説明しますので、今は読み飛ばすこととします。
2 行目以降は、関数の処理を表す部分です。これは関数が呼ばれる度に処理されます。通常の Python のコードと同じように書くことができますが、インデントをする必要がある点には注意してください。
復習となりますが、関数は関数名の後にカッコをつけることで呼び出すことができます。この場合では、以下のようになります。
print_numbers() # 1, 2, 3が改行されて出力される
なお、関数は定義した後でないと呼び出すことができません。その点には注意してください。たとえば、以下のようなコードは実行時エラーとなります。
print_one() # NameError: name 'print_one' is not defined def print_one(): print(1)
引数を取る関数定義
次に、min(1, 2)
のように値を渡すことができる関数を定義してみます。以下は、第一引数と第二引数を足した数を出力する関数の定義と、その実行結果です。
def add_and_print(a, b): print(a + b) add_and_print(1, 2) # 3 が出力される
新しく出てきた事項として、一行目の(a, b)
という記述があります。これは、関数の後に書かれた一番目の要素(第一引数)をa
というパラメータに、二番目の要素(第二引数)をb
というパラメータに格納することを意味しています。このパラメータは関数の処理部分で変数のように用いることができます。
よって、この関数をadd_and_print(1, 2)
のようにして呼び出した場合、a
には1
、b
には2
が格納され、その状態でprint(a + b)
が実行された結果として3が出力されることになります。
戻り値を持つ関数定義
ここまでの文法では min
のような関数を実装することはできません。これは、return
文を用いることで、関数の中から呼び出し側に値を返すことができます。このような関数の「結果」を戻り値とよびます。以下は、return
分を用いた第一引数に 1 を足した値を返す関数の定義です。
def add_one(a): return a + 1
return
文の後には、関数の戻り値としたい値を記述します。すると、その値が関数呼び出しの式の値となります。よって、以下のように変数に代入したり、直接関数の引数として渡すことも可能です。
two = add_one(1) # 変数 two は 2 となる print(two) # 2 が出力される print(add_one(1) + 1) # 3 が出力される
また、return
文は実行された時点で関数の処理を終了するという役割もあります。これについては、後ほど詳述します。
まとめ
まとめると、関数の定義は以下のような文法で行うこととなります。
def 関数名(パラメータ): 処理
実は、関数のパラメータにはここでは説明しなかった文法がいくつか存在しています。これを用いると、print
関数のように引数の数を自由にできたり、sep=''
のように名前で指定できたりするパラメータを作成できるようになります。
興味のある方は、Python公式ドキュメント内の用語集 - parameterを参照してください。
関数定義時のテクニック
return 文の特性と複数の return 文を持つ関数
return
文は、同じ関数内の複数の箇所に書くことができます。例えば、以下はa
とb
の小さい方を戻り値とする関数の定義です。
def my_min(a, b): if a < b: return a else: return b
if
文がどのようなものだったかを思い出すと、a
がb
より小さいときにreturn a
が、そうでないときにreturn b
が実行されることになると分かります。
return
文は、実行された時点で関数の処理を終了します。つまり、以下のadd_one
関数のreturn a + 1
以降に書かれた文が実行されることはありません。
def add_one(a): return a + 1 print("ここには到達しない") return a + 2 # この行にも到達しない print("もちろんここにも到達しない")
これを用いると、my_min
を以下のように書き換えることができます。
def my_min(a, b): if a < b: return a # a < b のケースでは return a が実行されているため、ここには到達しない return b
このコードでは、return b
が実行されるのはa < b
でないときのみです。何故ならば、a < b
のときはreturn a
が実行されるので、そこで関数の処理が終了するためです。
また、値を返さない関数1でも、関数の処理を早期に終了する目的でreturn
文を使用することができます。
def print_a_is_7(a): if a == 7: print("a is 7") return # a == 7 のケースでは return が実行されているため、ここには到達しない print("a is not 7")
例えば、上の例では a == 7
のときには print("a is not 7")
が実行されません。これは、a == 7
のときは return
が実行されているので、そこで関数の処理が終了するためです。
関数内の変数
通常のプログラムと同様に、関数内でも変数を用いることができます。しかし、関数内で使用した変数は、基本的には関数の外部で使用することはできません。
以下の関数は、add_one
関数を変数を使って書き直したものです。
def add_one(a): result = a + 1 return result print(add_one(1)) # 2 が表示される print(result) # NameError: name 'result' is not defined
この関数自体は、想定している通りの動作をします。しかし、関数を呼び出した後にresult
変数を参照しようとしても、存在しない変数を使用しようとしたことを示すエラーが発生してしまいます。
変数を使用できる範囲のことをスコープと呼びます。今回の場合、result
変数のスコープはadd_one
関数内であるということになります。
一方で、関数の外の変数は自由に使用することが可能です。以下は、変数a
の値をプリントするprint_a
関数を用いた例です。
a = 0 def print_a(): print(a) print_a() # 0 が表示される a = 1 print_a() # 1 が表示される
変数a
は関数の外で宣言されていますが、その値をprint_a
関数の内部で用いることができていることが分かります。これは、変数a
のスコープがグローバル(つまり、コード全体)であるためです。このような変数のことをグローバル変数と呼びます2。
一方で、関数内でグローバル変数を変更する場合には注意が必要です。以下は、受け取った変数をa
に代入することを意図した関数update_a
を用いた例です。
a = 0 def update_a(val): a = val update_a(1) print(a) # 0 が出力される
この例から分かる通り、単に代入文を書くだけでは関数内ではグローバル変数の値を変更することができません3。グローバル変数に値を代入したい場合は、global
文を使用します。
a = 0 def update_a(val): global a a = val update_a(1) print(a) # きちんと 1 が出力される
関数内にてglobal 変数名
という形でglobal
文を使用すると、関数内で指定した変数を使用した際にグローバル変数として扱うようになります。また、複数の変数を指定したい場合は、global a, b, c
のようにカンマで区切ることで指定することができます。
global
文が必要な条件については一見複雑そうに見えますが、まとめれば「関数外部でも変数を使用していて、かつ関数内で代入をしている」場合となります。
よく混乱する例として、list
等を代入せずに変更するケースがあります。例えば、以下のようなケースではglobal
文は必要ありません。
li = [] def update_li(val): li.append(val) update_li(1) print(li) # [1] と出力される
これは、変数li
は変更されてはいるものの代入されていないためです。global
文が必要となるケースは、以下のように変数li
に代入しているケースです。
li = [] def update_li(val): global li li = li + [val] update_li(1) print(li) # [1] と出力される
nonlocal
ここからは、global
文についての補足となります。複雑なプログラムを書かない内は必要となることがないであろう知識なので、読み飛ばしても構いません。
for
文の中にfor
文を入れることができたのと同様に、Pythonでは関数内で関数を定義することが可能です。この際、以下のように関数内で用いたい外部の変数がグローバル変数でない場合が存在します。
def get_1(): value = 0 def set_1(): # ここで用いている value はグローバル変数ではないので、global は使えない value = 1 # Error: local variable 'value' referenced before assignment set_1() return value
このような場合に使用できる文として、nonlocal
文が存在します。これを用いて上のコードを書き直すと、以下の通りとなります。
def get_1(): value = 0 def set_1(): nonlocal value value = 1 set_1() return value
問題
リンク先の問題を解いてください。
Time Limit: 0 msec / Memory Limit: 0 KiB
第1章修了
第1章お疲れ様でした。
1.00から1.13までの問題を全てACしたあなたは、理論上は全ての「計算処理」を行うプログラムを書ける程の知識を身に着けました。
第2章について
十分なプログラムの知識があったとしても、実際に複雑な計算をするプログラムを書くのは簡単ではありません。
第2章では「複雑な計算処理の書き方」や Python 特有の記法について解説します。
また、複雑な計算処理をコンピュータが行うのにかかる時間を見積もるための「計算量」という概念について説明します。
第2章をマスターすれば、実際に全ての「計算処理」を行うプログラムを書ける程の知識が身につきます。
ぜひコンテストに参加してみましょう!
AtCoder Beginner Contest
毎週土曜日の午後9時からは、AtCoder Beginner Contestが開催されていますので、ぜひコンテストに参加してみましょう。最新のコンテスト情報はこちらからご確認ください。
AtCoder Daily Training
AtCoder Beginner Contestの過去問を使用した練習用バーチャルコンテストです。
毎週火曜・水曜・木曜の夕方から夜にかけて1日3回行っています。
難易度はEASYをおすすめします。
AtCoderで強くなるには?
AtCoderのコンテストの復習方法やコンテスト以外での学習方法が書いてあります。
上記リンク先は、AtCoderInfo内のページです。
Time Limit: 0 msec / Memory Limit: 0 KiB
キーポイント
- for 文を使うとループ処理を行うことができる
- range 関数を使うと指定した範囲の処理を行うことができる
- リストの各要素に対して処理を行うこともできる
- enumerate, zip, reversed なども便利
R - 2.01.いろいろな for 文
for 文は第 1 章でも扱いましたが、より発展的な書き方も含めいろいろな書き方を紹介します。
range を使う書き方
1.09 節で扱った内容の復習です。 range
関数を使うと指定した範囲のループ処理を行うことができます。
range(stop)
stop
未満の整数をループします。
# 0 以上 5 未満をループ for i in range(5): print(i)
実行結果
0 1 2 3 4
range(start, stop)
range
関数に引数を 2 つ指定すると、その範囲をループします。
# 2 以上 5 未満をループ for i in range(2, 5): print(i)
実行結果
2 3 4
range(start, stop, step)
range の第 3 引数は、ループのステップを指定できます。
# 4 以上 10 未満の範囲を 2 ずつ増やしながらループ for i in range(4, 10, 2): print(i)
実行結果
4 6 8
第 3 引数に負の整数を指定した場合、数値を減らしながらループします。
# 5 から 0 まで降順にループ for i in range(5, -1, -1): print(i)
実行結果
5 4 3 2 1 0
リストなどに対する for 文
リストに対する for 文
単純なリストに対する for 文は 1.10 でも扱いました。
L = [10, 20, 40, 80, 160] # L の要素を順に出力 for x in L: print(x)
実行結果
10 20 40 80 160
文字列に対する for 文
文字列に対して for 文を使うと、 1 文字ごとに処理することができます。より一般的な型の変数に対する for 文については「発展的な内容」も参照してください。
S = "AtCoder" # s の要素を順に出力 for x in S: print(x)
実行結果
A t C o d e r
リストのリスト
リストのリスト
リストを要素とするリストの場合などには、カンマ区切りで受け取ると複数の変数に一気に格納することができます。
L = [[5, 10], [6, 20], [7, 50]] for a, b in L: print("a は", a, "です。 b は", b, "です。")
実行結果
a は 5 です。 b は 10 です。 a は 6 です。 b は 20 です。 a は 7 です。 b は 50 です。
3 要素以上の場合
内側のリストが 3 要素以上になっても同様に書けますが、受け取る側の引数の個数が異なるとエラーになるので注意してください。
L = [[1, 2, 3], [4, 5, 6]] for a, b, c in L: print("a は", a, "です。 b は", b, "です。 c は", c, "です。")
実行結果
a は 1 です。 b は 2 です。 c は 3 です。 a は 4 です。 b は 5 です。 c は 6 です。
次の例では長さ 4 の配列を 3 つの変数( a, b, c
)で受け取ろうとしているため、 ValueError: too many values to unpack (expected 3)
というエラーになります。
L = [[1, 2, 3, 4], [5, 6, 7, 8]] for a, b, c in L: print("a は", a, "です。 b は", b, "です。 c は", c, "です。")
enumerate
enumerate
関数を使うと、リストのインデックスと要素をまとめて取得できます。
L = [10, 20, 40, 80, 160] # L のインデックスと要素を取得 for i, x in enumerate(L): print(i, "番目の要素は", x)
実行結果
0 番目の要素は 10 1 番目の要素は 20 2 番目の要素は 40 3 番目の要素は 80 4 番目の要素は 160
インデックスは 0
から始まる点に注意してください。開始インデックスを明示的に指定したい場合は、第2引数に指定します。
L = [10, 20, 40, 80, 160] # インデックスを 10 から開始 for i, x in enumerate(L, 10): print(i, "番目の要素は", x)
実行結果
10 番目の要素は 10 11 番目の要素は 20 12 番目の要素は 40 13 番目の要素は 80 14 番目の要素は 160
リストを逆順に処理
reversed
関数を使うと、リストを逆順に処理することができます。
L = [10, 20, 40, 80, 160] # L の逆順 for a in reversed(L): print(a)
実行結果
160 80 40 20 10
リストの場合は reversed(L)
の代わりに 1.10 節で扱ったスライスを使って L[::-1]
と書いても逆順に処理することができます。
zip
複数のリストを同時に前からループ処理したい場合、 zip
関数が便利です。
A = [3, 4, 5, 6, 7] B = [10, 20, 40, 80, 160] # A と B から 1 つずつ要素を取得し a および b に格納する for a, b in zip(A, B): print(a, b)
実行結果
3 10 4 20 5 40 6 80 7 160
ふたつのリストの長さが異なる場合は短い方が終わるまで処理されます。
A = [3, 4, 5, 6, 7, 8, 9, 10] B = [10, 20, 40, 80, 160] # A と B から 1 つずつ要素を取得し a および b に格納する for a, b in zip(A, B): print(a, b)
実行結果
3 10 4 20 5 40 6 80 7 160
continue, break, else
こちらも 1.09 節で扱いましたが、復習のため再度紹介します。
continue
continue
はその後の処理を飛ばします。
for i in range(5): # i == 2 の場合はその後の処理を飛ばす if i == 2: continue print(i)
実行結果
0 1 3 4
break
ループを抜ける場合は break
を使います。
for i in range(5): # i == 2 の場合はループを抜ける if i == 2: break print(i)
実行結果
0 1
break-else
break
を含むループ処理の後に else
節を書くと、 break
で抜けなかった場合にのみ else
節が処理されます。
次の例では break
により抜けるので else
節は処理されません。
for i in range(5): if i == 2: break print(i) else: print("else 節が処理されたよ")
実行結果
0 1
次の例では break
により抜けないので、 else
節が処理されます。
for i in range(5): if i == 7: break print(i) else: print("else 節が処理されたよ")
実行結果
0 1 2 3 4 else 節が処理されたよ
発展的な内容
イテラブルオブジェクト
本節では主にリストや文字列の要素を順に処理するループを紹介しましたが、より一般的には イテラブル(iterable) なオブジェクトに対して同様の処理を行うことができます。イテラブルなオブジェクトとは、要素を一つずつ順番に取り出すことができるオブジェクトのことです。 list
、 tuple
、 str
、 set
、 dict
などがイテラブルオブジェクトに該当し、これらはfor文などで逐次的に処理できます。また本節では range
を使う書き方はおまじないのように説明しましたが、 range
もイテラブルオブジェクトのひとつです。
問題
リンク先の問題を解いてください。
Time Limit: 0 msec / Memory Limit: 0 KiB
キーポイント
- ループ構文の中にさらにループ構文があるものを多重ループと呼ぶ
- 多重ループを一度に抜けたい場合は、フラグ変数を用意してそれぞれのループを抜けるようにする必要がある
多重ループ
1.08 節で扱った while 文や 1.09 節で扱った for 文などのループは、さらにその中にループを書くことができます。
for i in range(3): for j in range(3): print(i, j)
実行結果
0 0 0 1 0 2 1 0 1 1 1 2 2 0 2 1 2 2
このように、ループの内側にループがあるようなものを二重ループと呼びます。
二重だけでなく、当然三重ループや四重ループなどもあります。それらをまとめて多重ループと呼びます。
for i in range(2): for j in range(2): for k in range(2): print(i, j, k)
実行結果
0 0 0 0 0 1 0 1 0 0 1 1 1 0 0 1 0 1 1 1 0 1 1 1
二重ループを使って次の例題を解いてみましょう。
例題
要素数 3 の 2 つの数列 A, B に同じ要素が含まれているかどうかを判定してください。
入力の形式
A_1 A_2 A_3 B_1 B_2 B_3
入力例
1 3 2 4 5 3
出力例
YES
次のプログラムをベースに説明していきます。
# スペース区切りの整数をリストとして受け取る A = list(map(int, input().split())) # スペース区切りの整数をリストとして受け取る B = list(map(int, input().split())) # 答えを保持する変数 answer = False # ここにプログラムを追記 if answer: print("YES") else: print("NO")
「A と B の要素の組み合わせを全て見て、一致しているものがあるかを調べる」という方針で解くことにしましょう。
まず、ループを使わないで書いてみます。
# スペース区切りの整数をリストとして受け取る A = list(map(int, input().split())) # スペース区切りの整数をリストとして受け取る B = list(map(int, input().split())) # 答えを保持する変数 answer = False if (A[0] == B[0] or A[0] == B[1] or A[0] == B[2] or A[1] == B[0] or A[1] == B[1] or A[1] == B[2] or A[2] == B[0] or A[2] == B[1] or A[2] == B[2]): answer = True if answer: print("YES") else: print("NO")
A
に着目してパターン化すると次のような形式になっていることがわかります。
A[i] == B[0] or A[i] == B[1] or A[i] == B[2]
これをループ化してみます。
# スペース区切りの整数をリストとして受け取る A = list(map(int, input().split())) # スペース区切りの整数をリストとして受け取る B = list(map(int, input().split())) # 答えを保持する変数 answer = False for i in range(3): if A[i] == B[0] or A[i] == B[1] or A[i] == B[2]: answer = True if answer: print("YES") else: print("NO")
次は B
に着目してパターン化します。
A[i] == B[j]
最終的なプログラムは次のようになります。
# スペース区切りの整数をリストとして受け取る A = list(map(int, input().split())) # スペース区切りの整数をリストとして受け取る B = list(map(int, input().split())) # 答えを保持する変数 answer = False for i in range(3): for j in range(3): if A[i] == B[j]: answer = True if answer: print("YES") else: print("NO")
多重ループの break/continue
for 文や while 文には break や continue という命令がありました。多重ループはループ文の中にループ文を入れたものなので、通常の for 文と同様に break/continue 命令を使うことができます。
多重ループで break 命令を使うと 1 段階ループを抜けることができます。内側のループの中で break すると内側のループを抜けることができますが、この break によって外側のループを抜けることはできません。
例えば、「i と j が両方 1 になったらループを抜ける」という処理を以下のようなコードで書いた場合、実行結果は意図したものにはなりません。
for i in range(3): for j in range(3): print(i, j) if i == 1 and j == 1: print("i と j が両方 1 なので終了(?)") break
実行結果
0 0 0 1 0 2 1 0 1 1 i と j が両方 1 なので終了(?) 2 0 2 1 2 2
多重ループを抜ける方法はいくつかありますが、そのうちのひとつとして「ループを抜けるかどうかを管理する変数(フラグ変数)を用意し、フラグ変数の値に応じてループを抜けるかどうか分岐する」という方法があります。
「i と j が両方 1 になったらループを抜ける」という処理を、フラグ変数を持つ方法で書くと以下のようになります。
# 外側のループを抜ける条件を満たしているかどうか(フラグ変数) finished = False for i in range(3): for j in range(3): print(i, j) if i == 1 and j == 1: print("i と j が両方 1 なので終了。") finished = True if finished: break if finished: break
実行結果
0 0 0 1 0 2 1 0 1 1 i と j が両方 1 なので終了。
また、フラグ変数を持つ方法以外に、多重ループの部分を関数化し return を用いて抜ける方法もあります。
def func(): for i in range(3): for j in range(3): print(i, j) if i == 1 and j == 1: print("i と j が両方 1 なので終了。") return func()
実行結果
0 0 0 1 0 2 1 0 1 1 i と j が両方 1 なので終了。
問題
リンク先の問題を解いてください。
Time Limit: 0 msec / Memory Limit: 0 KiB
キーポイント
- (リスト内包表記)
[(変数を使った処理) for (変数名) in (変数を動かす範囲)]
のように書くことで変数を使った処理
の結果を要素に持ち、- 変数は for 文で指定の範囲を動く
リストを取得できる。
* (標準入力から数値リストを取得)
数値の列を標準入力から受け取ってリストにする処理は
[int(item) for item in input.split()]
として記述できる
* (フィルタリング)
[(変数を使った処理) for (変数名) in (変数を動かす範囲) if (条件式)]
のように書くことで
* 変数を使った処理
の結果を要素に持ち、
* 変数は for 文で指定の範囲を動くが、
* 条件式
が True の範囲のみに絞り込んだ
リストを取得できる。
- 多重の for 文や内包表記をネストさせることも可能。
リストの内包表記 : for
のもう一つの使い方
今回は for
の新しい使い方について説明します。
以下のコードの方法1は今までの for
の使い方によって5つの要素からなるリストを作っていますが、方法2のように書き換えることが可能です。
# 方法1 : 従来の for 文 l = [] for i in range(5): l.append(i*i) # 方法2 : リスト内包表記 l2 = [i*i for i in range(5)] # l と l2 はともに [0, 1, 4, 9, 16] であり、等しい print(l) print(l2)
出力
[0, 1, 4, 9, 16] [0, 1, 4, 9, 16]
この [i*i for i in range(5)]
は新しいリストの作成を簡潔に記述するための記法で、リスト内包表記と呼びます。この部分は次のように解釈すると読みやすいでしょう:
i*i
を要素に持つリストであるi
はrange(5)
の範囲 (=0,1,2,3,4) を動く- 結果として、
[0*0, 1*1, 2*2, 3*3, 4*4]
すなわち[0, 1, 4, 9, 16]
というリストが作成される
より一般的に、リスト内包表記では
[(変数を使った処理) for (変数名) in (変数を動かす範囲)]
のように記述します。
これによって、
変数を使った処理
の結果を要素に持ち、- 変数は for 文で指定の範囲を動く
ような要素からなるリストを得ることができます。
※ 注意として、四角括弧([...]
)ではなく丸括弧((...)
)で内包表記を書いてしまうと結果はリストではなくジェネレータという別の型になってしまいます。この場合リストに対して行えるようなスライスなどの操作が行えなくなってしまいます。
l = (i*i for i in range(5)) print(l[0])
出力
TypeError Traceback (most recent call last) <ipython-input-5-38cb2059caa6> in <module> 1 l = (i*i for i in range(5)) ----> 2 print(l[0]) TypeError: 'generator' object is not subscriptable (訳 : 「ジェネレータ」オブジェクトは添字アクセスできません)
競技プログラミングの文脈でジェネレータが必要になることは稀ですので、内包表記はリスト([...]
)の記法で書く、と覚えてしまうのがよいでしょう。
リスト内包表記の例
内包表記は全く新しい記法になるため慣れるのに時間がかかると思いますが、慣れると非常に便利な記法ですのでぜひ習熟しておくとよいでしょう。
下記に内包表記を使ったコード例を示します:
# 例1. range(3) の要素それぞれに 1 を足す処理をする -> [1,2,3] l = [hoge+1 for hoge in range(3)] # 例2. l の要素それぞれに 2 倍する処理をする -> [2,4,6] l2 = [2*i for i in l] # 例3. l2 の要素それぞれに、「要素とそれから 1 を引いた値の2要素からなるリストを作る」処理をする -> [[2,1], [4,3], [6,5]] l3 = [[val, val - 1] for val in l2]
例1. のように、変数の名前は最初の宣言と for 文の中で整合していれば何でも良いです1。
例2. のように、変数を動かす範囲
は range
に限らず 2.01 節で説明したような色々な記述方法を使うことができます。この例では既に作成したリストを動かす範囲に指定しています。
例3. のように、変数を使った処理の結果
は数値に限らず任意の型の要素をとることができます。この例では処理の結果としてリストが得られています。そのため、リスト内包表記全体として得られるものはネストしたリスト(二重リスト)になっています。
もうひとつ例を紹介します。空白で区切られた数値の列を入力を受け取るために内包表記を使うことができます。
次の例は入力として与えられる数値の列をリストで受け取り、その合計を出力します:
入力
3 1 4 1 5
l = [int(item) for item in input.split()] print(sum(l))
出力
14
この例のように、内包表記の「変数を使った処理」で記述する処理は関数呼び出しを使うことも可能です。
組み込み関数に渡す
1.12節で扱った組み込み関数に対し、内包表記で引数を与えることが可能です。
l = [-3, -1, 1, 2] a = max(v*v for v in l) b = min(v*v for v in l) c = sum(v*v for v in l) print(a,b,c)
出力
9 1 15
上記の例ではリストの要素をそれぞれ二乗した値の最大 / 最小 / 総和を max
/ min
/ sum
関数を使って計算しています。これらの関数は内包表記2を引数として受け取れるように実装されているため、このような書き方が可能です。
もちろん、a = max([v*v for v in l])
のようにリスト内包表記で作ったリストを関数に渡しても同じ結果になります。
この場合リストを作る処理が一度行われるため、計算速度・メモリ使用量が僅かではありますが大きくなります。
if 文によるフィルタリング
内包表記の for 文では直後に if 文をつなげて書くことが可能です。この場合、リストの要素は if 文の判定が真である要素のみに絞り込まれ(フィルタリングされ)ます。
ひとつ例を示します:
l = [3, 1, 4, 1, 5] l_only_even = [v for v in l if v%2==0] l_only_odd = [v for v in l if v%2==1] print(l_only_even) print(l_only_odd)
出力
[4] [3, 1, 1, 5]
この例では与えられたリストに対し、そのうち偶数/奇数であるもののみからなるリストを作成しています。
l_only_odd
のように該当する要素が複数ある場合、元のリストにおける順番で取得されます。
整理すると、
[(変数を使った処理) for (変数名) in (変数を動かす範囲) if (条件式)]
のように書くことで
* 変数を使った処理
の結果を要素に持ち、
* 変数は for 文で指定の範囲を動くが、
* 条件式
が True の範囲のみに絞り込んだ
ような要素からなるリストを得ることができます。
(発展) 複雑な内包表記
発展として、より複雑な内包表記の記法について説明します。
ただし、ここに示す書き方はコードを短く書くには便利ですが、内包表記を使わずに同じ処理を実現することもできるため、必ずしも習得する必要はありません。
興味のある方はご覧ください。
二重ループを伴う内包表記
前節で二重ループを扱いましたが、内包表記でも同様に書くことが可能です。
次の例は2つのリストが与えられたときに、それぞれから1つの要素を取るリストを要素に持つ二重リストを計算しています:
xs = [1, 2, 3] ys = ["a", "b", "c"] xys = [] for x in xs: for y in ys: xys.append([x,y]) print(xys)
出力
[[1, 'a'], [1, 'b'], [1, 'c'], [2, 'a'], [2, 'b'], [2, 'c'], [3, 'a'], [3, 'b'], [3, 'c']]
このコードは次のように内包表記で書き換えることが可能です。
xs = [1, 2, 3] ys = ["a", "b", "c"] xys = [[x,y] for x in xs for y in ys] print(xys)
多重の for 文の場合でも、for 文を通常通り書いたものをリストの中に入れ込めばよいです。
若干複雑な記法になるため、難しく感じる場合は冒頭のように二重ループを使って書くほうが確実でしょう。
ネストした内包表記
先程のコードを少し変えた例を紹介します。次のコードはどのような出力になるでしょうか?
xs = [1, 2, 3] ys = ["a", "b", "c"] xys = [[[x,y] for x in xs] for y in ys] print(xys)
このコードでは内側のリスト内包表記 [(x,y) for x in xs]
が外側のリスト内包表記における 変数を使った処理
に相当します。
そのため、リストを要素として持つリスト、すなわち二重リストが生成されます:
出力
[[[1, 'a'], [2, 'a'], [3, 'a']], [[1, 'b'], [2, 'b'], [3, 'b']], [[1, 'c'], [2, 'c'], [3, 'c']]]
ネストした内包表記が複雑だと感じる場合、以下のように二重ループを使うか、1つのループと1つの内包表記に分解することもできます:
xys = [] for y in ys: item = [] for x in xs: item.append([x,y]) xys.append(item)
xys = [] for y in ys: xys.append([[x,y] for x in xs])
はじめのうちは自分が書きやすいと感じる方法を使って慣れていくのが良いでしょう。
二重ループとフィルタリングとの組み合わせ
上述した二重ループを伴う内包表記、ネストした内包表記はいずれも if 文を用いたフィルタリングと組み合わせることが可能です。
二重ループとフィルタリングの組み合わせの例を以下に示します:
xs = [1,2,3] ys = [4,5,6] # 和が3の倍数である (x,y) のリスト xys_filter = [[x,y] for x in xs for y in ys if (x+y)%3==0] print(xys_filter)
出力
[[1, 5], [2, 4], [3, 6]]
二重ループにフィルタリングも組み合わせると、慣れているプログラマにとっても読みづらくなるため、あまり推奨はしません。このような場合は通常の for 文で記述したほうが良いでしょう。
この例は次のようにすれば内包表記を使わずに記述することができます:
xys_filter2 = [] for x in xs: for y in ys: if (x+y)%3==0: xys_filter2.append([x,y])
問題
リンク先の問題を解いてください。
Time Limit: 0 msec / Memory Limit: 0 KiB
キーポイント
- 二次元配列 = リストを要素として持つリストで、数値が縦横に並んだデータ
- 二次元配列
a
に対してa[i][j]
とすることでその i 行 j 列の要素にアクセスできる - 二次元配列
a
に対してlen(a)
とすることでa
の行数を、len(a[0])
とすることでa
の列数を取得できる [list(map(int, input().split())) for _ in range(N)]
とすることで行数 N の二次元配列を入力から受け取ることができる[[0]*M for _ in range(N)]
とすることですべての値が 0 のサイズ N \times M の二次元配列を作ることができる[[] for _ in range(N)]
とすることでサイズ N\times 0 の二次元配列(空リストを N 個含むリスト)を作ることができる
二次元配列
第一章では複数の要素を含むデータ構造であるリストを扱いました。
l = [1,3,5,7]
実はリストの要素は数値に限る必要はありません。次のように様々な型の要素を並べて保持することが可能です:
l = [1, None, "hoge", False]
特に、「数値を要素に持つリスト」を要素として持つリストのことを二次元配列といいます。
a = [[1,3,5],[2,4,6]]
この場合、リストの 0 番目の要素は [1,3,5]
というリスト、1 番目の要素は [2,4,6]
というリストになります。
print(a[0]) print(a[1])
出力
[1, 3, 5] [2, 4, 6]
このように数値が行方向・列方向に並んだものは数学においては行列と呼ばれますが、競技プログラミングにおいても頻出するデータ構造になります。
行列の中の数値に直接アクセスしたい場合、カッコをつなげることでが可能です。
print(a[0][0]) print(a[1][1]) print(a[0][-1])
出力
1 4 5
二次元配列のイメージがうまく湧かない方は C++ 版のこちらの記事も参考にしてみてください。
二重ループと二次元配列
理解を深めるために、2.02節で扱った多重 for 文を利用することで二次元配列の要素を出力してみましょう1。
a = [[1,3,5],[2,4,6]] for i in range(len(a)): print(f"リストの第 {i} 成分は {a[i]} です") for j in range(len(a[i])): print(f"リストの第 ({i}, {j}) 成分は {a[i][j]} です")
出力
リストの第 0 成分は [1, 3, 5] です リストの第 (0, 0) 成分は 1 です リストの第 (0, 1) 成分は 3 です リストの第 (0, 2) 成分は 5 です リストの第 1 成分は [2, 4, 6] です リストの第 (1, 0) 成分は 2 です リストの第 (1, 1) 成分は 4 です リストの第 (1, 2) 成分は 6 です
上記の例では行 (i) 方向と列 (j) 方向について二次元配列全体を走査しています。
具体的には以下のような処理が行われています:
len(a)
とすることで二次元リストの長さ(ここでは2)を取得していますi
を 0 以上len(a)
未満の整数値で動かしながらa[i]
とすることで内側のリストにアクセスしていますlen(a[i])
とすることで内側のリストの長さ(ここでは常に3)を取得していますj
を 0 以上len(a[i])
未満の整数値で動かしながらa[i][j]
とすることで内側のリストの要素(数値)にアクセスしています
練習も兼ねて、同じことをするプログラムの別の書き方の挙げます:
a = [[1,3,5],[2,4,6]] for item in a: print(item) for val in item: print(val)
出力
[1, 3, 5] 1 3 5 [2, 4, 6] 2 4 6
こちらの例では以下のような流れで二次元リスト全体を走査しています:
* for item in a:
とすることで変数 item
に二次元配列の要素(=内側のリスト)を順に格納しています
* for val in item
とすることで内側のリストの要素(=数値)を変数 val
に順に格納しています
二次元配列を入力から取得する
競技プログラミングでは入力として二次元配列が与えられることがしばしばあります。
練習として、次のような入力が与えられる状況を考えます:
N M A_{11} A_{12} \cdots A_{1M} A_{21} A_{22} \cdots A_{2M} \vdots A_{N1} A_{N2} \cdots A_{NM}
1行目に与えられる N は二次元配列の行の数を、M は二次元配列の列の数を示しており、2行目以降には二次元配列の値が N 行にわたって与えられます。各行は M 個の整数からなっています。
この入力から二次元配列を取得してみましょう。以下に3つの方法を示します:
方法 1
N, M = list(map(int, input().split())) a = [] for i in range(N): a.append(list(map(int, input().split())))
方法 2
N, M = list(map(int, input().split())) a = [None]*N for i in range(N): a[i] = list(map(int, input().split()))
方法 3
N, M = list(map(int, input().split())) a = [list(map(int, input().split())) for _ in range(N)]
出力
[[1, 3, 5], [2, 4, 6]]
3つの方法はいずれも、1行目で N, M の値を受け取っています。
list(map(int ...
の書き方は 1.10 節 で扱った、1行に空白区切りで与えられる数値をリストで取得する方法です。1行の中の要素数(ここでは 2)がわかっている場合はこのように 2 つの変数に直接代入することが可能です。
3つの方法それぞれ2行目以降で二次元配列を受け取っています。
- 方法1 においては
a
を最初に空のリストとして定義し、 N 個与えられる行を都度リストに変換してa
にappend
することで二次元配列を得ています。 - 方法2 においては
a
を長さ N のリストとして定義し、i を 0 以上 N 未満の範囲で動かし、N 個与えられる行を都度リストに変換してa[i]
に代入することで二次元配列を得ています。 - 方法3 は前節で扱った内包表記を用いており、1行を読み込んでリストにする処理を N 回行い、二次元配列を得ています。
二次元配列を作成する
要素がすべて 0 の二次元配列を作る
競技プログラミングでは要素がすべて 0 の二次元配列を用意したくなることがしばしばあります。
以下のように書くことでサイズ N \times M の二次元配列を作ることができます:
N = 2 M = 3 a = [[0 for _ in range(M)] for _ in range(N)] # a = [[0] * M for _ in range(N)] としてもよい print(a)
出力
[[0, 0, 0], [0, 0, 0]]
この例では内側の [0 for _ in range(M)]
2 が 0 を M 回取得したリスト、すなわちサイズ M の配列を作る処理になっており、それを N 回行うことでサイズ N \times M の二次元配列を作っています。
コメントしているように、内側のサイズ M の配列を作る処理は [0] * M
のように記述することも可能です。
N\times 0 の二次元配列を作る
後から配列に要素を追加して使う場合などに N\times 0 の配列を宣言することがあります。
以下のように書くと、N\times 0 の二次元配列になります。
N = 10 a = [[] for _ in range(N)] print(a)
出力
[[], [], [], [], [], [], [], [], [], []]
この書き方では内包表記で「空のリストを作る」処理を N 回繰り返すことで N\times 0 の二次元配列を作成しています。
二次元配列を作成する際の注意
行数と列数の指定方法の注意
サイズ N \times M の二次元配列を作る際に、[[0 for _ in range(N)] for _ in range(M)]
のように、N と M を逆にしてしまわないように気を付けてください。内包表記の動作から、最後の range
の中に書いた数値が外側のリストのサイズになります。そのため、N を最後に書くことになります。
二次元配列の初期化方法の注意
サイズ N \times M の二次元配列を初期化する際、次のように書いてしまわないように注意してください:
a = [[0] * M] * N
このように書くと配列自体は作成できますが、いずれかのリストに対する変更が全てのリストに共有されるという不具合が生じてしまいます:
a = [[0] * 2] * 3 print("更新前:", a) a[0][0] = 1 print("更新後:", a)
出力
更新前: [[0, 0], [0, 0], [0, 0]] 更新後: [[1, 0], [1, 0], [1, 0]]
上記のコードでは (0,0) 成分に 1 を代入したため、[[1, 0], [0, 0], [0, 0]]
となることが期待されますが、出力を見ると配列の1列目の値がすべて 1 に変更されてしまっています。
この原因の詳細は「ポインタ」等の未解説の概念が必要になるため省略しますが、この記法では二次元配列内のリストがすべて同じ実体を共有するためこのようなことが起こってしまいます。
同様に、N\times 0 の二次元配列を作る際に以下のように書いてしまわないように注意してください:
N = 10 a = [[]] * N
このように書くと、二次元配列内の空リストがすべて同じ実体を持つため、いずれかのリストに対する変更がすべてのリストに共有されてしまいます:
N = 10 a = [[]] * N print("更新前:", a) a[0].append(100) print("更新後:", a)
出力
更新前: [[], [], [], [], [], [], [], [], [], []] 更新後: [[100], [100], [100], [100], [100], [100], [100], [100], [100], [100]]
各要素の長さが異なる二次元配列
これまでの例では二次元配列の中のすべてのリストが同じ長さを持っていましたが、リストを使っていれば各要素の長さは異なっていても問題ありません。
a = [[] for _ in range(4)] a[0].append(10) a[0].append(20) a[2].append(30) a[3].append(40) a[3].append(50) a[3].append(60) print(a)
出力
[[10, 20], [], [30], [40, 50, 60]]
多次元配列
二次元配列を拡張して、三次元、四次元... の配列を作ることも可能です。
次の例では三次元配列を内包表記によって初期化し、次に三重ループでその要素を書き換えています:
N = 2 M = 2 D = 2 lst3d = [[[0]*D for _ in range(M)] for _ in range(N)] for i in range(N): for j in range(M): for k in range(D): lst3d[i][j][k] = (i,j,k) print(lst3d)
出力
[[[(0, 0, 0), (0, 0, 1)], [(0, 1, 0), (0, 1, 1)]], [[(1, 0, 0), (1, 0, 1)], [(1, 1, 0), (1, 1, 1)]]]
[[[0]*D for _ in range(M)] for _ in range(N)]
の部分で三次元配列を作っています。
この中の [[0]*D for _ in range(M)]
はサイズ M \times D の配列を作っています。この処理を後ろから for _ in range(N)
で囲むことで、「サイズ M \times D の配列 N 個からなるリスト」すなわちサイズ N \times M \times D の配列を得ています。
より一般に、K 次元配列を作るためには一般には複数の K-1 次元配列を作り、それをリストに入れればよいです。
ABCの問題
ここまでの知識で解ける問題をAtCoder Beginner Contestの過去問題から紹介します。練習問題だけでは物足りない人は挑戦してみてください。
問題
リンク先の問題を解いてください。
Time Limit: 0 msec / Memory Limit: 0 KiB
キーポイント
- 関数の中で自身を呼び出すことを 再帰呼び出し と言う
- 再帰呼び出しをする関数を 再帰関数 と言う
sys.setrecursionlimit
で再帰深さの上限を変更できる
再帰関数を理解するためには関数を理解している必要があります。 1.13. 関数 を復習しておきましょう。
再帰関数は難しいので、説明を読んでみて分からなかった場合はそのまま次に進んでもかまいません。
再帰関数
関数の中で自身を呼び出すことを 再帰呼び出し と言います。
また、再帰呼び出しをする関数を 再帰関数 と言います。
例えば、0 から n までの整数の総和を計算する関数 sum_triangle
を考えます。
以下のように実装された関数 sum_triangle
は、その処理の中で sum_triangle
を呼び出しているので、再帰関数であると言えます。
# 0 から n までの総和を求める def sum_triangle(n): if n == 0: return 0 # 0 から 0 までの総和は 0 s = sum_triangle(n - 1) # 自身を呼び出して 0 から n-1 までの総和を求める return s + n # n を足して 0 から n までの総和を求める
sum_triangle(3)
を呼び出したときの処理の様子を見てみましょう。
以下のスライドは C++ でコードが書かれていますが、やっていることは同じです。
再帰関数の 3 つの部分
再帰関数は、以下の 3 つの部分に分けることができます。
- ベースケース : 簡単に答えが求められる小さいケースは、再帰呼び出しをせず直接答えを求める。
- 再帰呼び出し : 自身を呼び出して、1 つ小さなケースの答えを計算する。再帰呼び出しを繰り返すことで、必ずベースケースに到達することができる。
- 答えの計算 : 再帰呼び出しの結果を利用して、答えを計算する。
def sum_triangle(n): if n == 0: # ベースケース return 0 s = sum_triangle(n - 1) # 再帰呼び出し return s + n # 答えの計算
sum_triangle
の例では、n == 0
の場合がベースケースで、n > 0
のときは再帰呼び出しをして答えを計算しています。
再帰呼び出しのたびに n が 1 小さくなるので、(最初に 0 以上の整数が与えられた場合は)必ずベースケースに到達します。
この条件が満たされない場合、再帰呼び出しをどれだけ繰り返してもベースケースに到達しないので、無限ループになって RE や TLE が発生することに注意しましょう。
再帰関数の例
いくつかの再帰関数の例を見てみましょう。これらの例は、再帰関数を使わず for 文や while 文を使って実装することも可能です。
階乗
# 入力 : 0 以上の整数 n # 出力 : n の階乗 (1 から n までの総積) を返す def factorial(n): # ベースケース : 0 の階乗は 1, 1 の階乗は 1 if n == 0 or n == 1: return 1 # 再帰呼び出し : 1 から n-1 までの総積を計算する s = factorial(n-1) # 答えの計算 : 1 から n までの総積を計算する return s * n
この例では、再帰呼び出しのたびに n が 1 小さくなるので、必ずベースケースに到達します。
フィボナッチ数
# 入力 : 0 以上の整数 n # 出力 : fib(0) = 1, fib(1) = 1, fib(n) = fib(n-1) + fib(n-2) で定められた数列の n 項目を返す def fib(n): # ベースケース : fib(0) = 1, fib(1) = 1 if n == 0 or n == 1: return 1 # 再帰呼び出し : fib(n-1) と fib(n-2) を計算する f1 = fib(n-1) f2 = fib(n-2) # 答えの計算 : fib(n) を計算する return f1 + f2
この例では、再帰呼び出しのたびに n が 1 または 2 小さくなるので、必ずベースケースに到達します。
各桁の和
# 入力 : 0 以上の整数 n # 出力 : n を 10 進法で表記したときの各桁の和 def digit_sum(n): # ベースケース : n が 1 桁ならば、各桁の和は n である if n <= 9: return n # n を 1 の位とそれより上の位に分ける ones = n % 10 # 1 の位は、n を 10 で割った余りで得られる other = n // 10 # 1 の位を取り除いて左に詰めることは、10 で割って切り捨てることに相当する # 再帰呼び出し : 1 より上の位の各桁の和を求める s = digit_sum(other) # 答えの計算 : 各桁の和を計算 return s + ones
この例では、再帰呼び出しのたびに n の桁数が 1 小さくなるので、必ずベースケースに到達します。
再帰関数の設計
再帰関数がどのようなものかが分かってきたでしょうか?
再帰関数を実装するには、適切な再帰呼び出しを設計する必要があります。
ここでは、sum_triangle
を例として、再帰関数の設計方法を説明します。
1. 入力と出力を決める
まずは、関数の入力(引数)と出力(戻り値)を決めます。sum_triangle
の場合は、以下のようになります。
- 入力(引数): 0 以上の整数 n
- 出力(戻り値): 0 から n までの総和
2. 再帰呼び出しを設計する
次に、再帰呼び出しを使った計算の方法を考えます。
出力を計算するために、「引数を少し小さくして再帰呼び出しした結果」を利用できないか考えてみましょう。
sum_triangle
の場合、sum_triangle(n-1)
は「 0 から n-1 までの総和」を返すので、これに n を加えれば「 0 から n までの総和」を計算できることが分かります。
3. ベースケースを考える
次に、どのような場合がベースケースなのか、つまり「再帰呼び出しを行わずに出力を計算できるか」を考えます。
sum_triangle
の場合、n = 0 のとき「 0 から 0 までの総和」は 0 と簡単に計算できるので、これがベースケースとなります。
n = 1, 2 などのケースをベースケースに加えても問題はありませんが、n = 0 がベースケースに含まれていれば sum_triangle(1)
や sum_triangle(2)
は再帰呼び出しで計算できるので、加える必要はありません。
4. 必ずベースケースに到達することを確認する
最後に、再帰呼び出しを繰り返すことでいつかベースケースに到達することを確認しましょう。
sum_triangle
の場合、再帰呼び出しのたびに n が 1 小さくなるので、いつか必ず n = 0 に到達します。
【例題】報告書の伝達時間
この問題は難易度が高めなので、少し考えて分からなかったらヒントや解答例を見るようにしてください。
今までの例は for 文や while 文を使って実装することもできましたが、この例は再帰関数を使わずに実装することが難しく、再帰関数の恩恵を感じることができます。
問題文
あなたは A 社を経営する社長です。 A 社は N 個の組織からなり、それぞれに 0 から N-1 までの番号がついています。組織 0 はトップの組織です。
組織間には親子関係があり、組織 0 以外の N - 1 個の組織にはそれぞれ 1 つの親組織が存在します。
組織 i\ (1 ≤ i ≤ N - 1) の親組織は組織 p_i です。
ここで、ある組織からその組織の親組織をたどることを繰り返すと、必ずトップの組織(組織 0)に到達します。
あなたは全ての組織に報告書を提出するように求めました。
混雑を避けるために、「各組織は子組織の報告書がそろったら、自身の報告書を加えて親組織に送る」ことを繰り返します。子組織が無いような組織は自身の報告書だけをすぐに親組織に送ります。
ある組織から報告書を送ってから、その親組織が受け取るまでに 1 分の時間がかかります。
あるタイミングで一斉に報告書の伝達を開始したときに、トップの組織の元に全ての組織の報告書が揃う時刻(伝達を始めてから何分後か)を求めてください。なお、各組織の報告書は既に準備されているため、報告書の伝達以外の時間はかからないものとします。
制約
- 1 ≤ N ≤ 50
- 0 ≤ p_i ≤ N - 1\ (1 ≤ i ≤ N - 1)
- ある組織からその組織の親組織をたどることを繰り返すと、必ずトップの組織(組織 0)に到達する。
- 入力される値はすべて整数である。
入力
入力は以下の形式で標準入力から与えられる。
N p_1 p_2 \cdots p_{N-1}
p_0 が存在しないことに注意せよ。
出力
一斉に報告書の伝達を開始してから、トップの組織の元に全ての組織の報告書が揃うまでの時間を出力せよ。
入力例 1
6 0 0 1 1 4
出力例 1
3
この入力例では、組織は次のような関係になっています。
- 組織 1 の親組織は組織 0
- 組織 2 の親組織は組織 0
- 組織 3 の親組織は組織 1
- 組織 4 の親組織は組織 1
- 組織 5 の親組織は組織 4
この関係は次のような図になります。(子組織から親組織の向きに矢印が向いています。)
次の図は、子組織からの報告書が揃った時刻(集まった報告書を親組織へ送った時刻)を青い文字で、各子組織から受け取った時刻を赤い文字で書き込んだものです。
この図から分かるように、トップの組織の元に全ての組織の報告書が揃う時刻は 3 となります。
入力例 2
8 7 4 0 3 2 4 2
出力例 2
5
下記のサンプルプログラムを書き換え、次のスライドのような動作をするようにプログラムを完成させてください。
スライドは入力例 1 のイメージを示したものです。スライドでは引数として children
を受け取っていますが、これは必要ありません。
サンプルプログラム
N = int(input()) # p[i] : 組織 i の親組織 p = [-1] + list(map(int, input().split())) # 2 次元配列 children # children[i] : 組織 i の子組織の一覧 であるような 2 次元配列 children = [[] for _ in range(N)] for i in range(1, N): children[p[i]].append(i) # 再帰関数 complete_time # 入力 : 組織番号 x # 出力 : 組織 x に子組織からの報告書が揃った時刻(報告書を親組織へ送った時刻) def complete_time(x): # ここに実装する ... # 組織 0 の元に報告書が揃う時刻を出力 print(complete_time(0))
ヒント
クリックでヒントを開く
解答例
自分で考えてみたあと、必ず確認してください。
クリックで解答例を開く
N = int(input())
# p[i] : 組織 i の親組織
p = [-1] + list(map(int, input().split()))
# 2 次元配列 children
# children[i] : 組織 i の子組織の一覧
children = [[] for _ in range(N)]
for i in range(1, N):
children[p[i]].append(i)
# 再帰関数 complete_time
# 入力 : 組織番号 x
# 出力 : 組織 x に子組織からの報告書が揃った時刻(報告書を親組織へ送った時刻)
def complete_time(x):
# ベースケース : 子組織が存在しない場合、答えは 0
if len(children[x]) == 0:
return 0
# 子組織が存在する場合、答えは「子組織から報告書が届く時刻」の最大値
return max(complete_time(y) + 1 for y in children[x])
# 組織 0 の元に報告書が揃う時刻を出力
print(complete_time(0))
この問題における組織のような構造を 木構造 といいます。 木構造に関する処理を行う際には再帰関数が有用です。
再帰関数の注意点
再帰深さの上限
Python では、後述するスタックオーバーフローを防ぐために再帰の深さに制限がかけられており、デフォルトでは、1000 回より多く関数呼び出しが連なると RecursionError
が発生して RE になります。
実行するコード
def sum_triangle(n): if n == 0: return 0 s = sum_triangle(n - 1) return s + n print(sum_triangle(1000))
エラー出力
Traceback (most recent call last): File "/judge/Main.py", line 7, in <module> print(sum_triangle(1000)) ^^^^^^^^^^^^^^^^^^ File "/judge/Main.py", line 4, in sum_triangle s = sum_triangle(n - 1) ^^^^^^^^^^^^^^^^^^^ File "/judge/Main.py", line 4, in sum_triangle s = sum_triangle(n - 1) ^^^^^^^^^^^^^^^^^^^ File "/judge/Main.py", line 4, in sum_triangle s = sum_triangle(n - 1) ^^^^^^^^^^^^^^^^^^^ [Previous line repeated 996 more times] RecursionError: maximum recursion depth exceeded (訳) 再帰エラー: 最大再帰深さを超えました
終了コード
256
エラー出力を見ると、4 行目の sum_triangle(n - 1)
の再帰呼び出しが 999 回繰り返されて、sum_triangle(1000)
の呼び出しと合わせて 1000 回となって最大再帰深さを超えていることが分かります。
再帰深さの上限を変更するには、sys.setrecursionlimit
を実行します。
実行するコード
import sys sys.setrecursionlimit(10 ** 6) # 再帰深さの上限を 1000000 回に変更する def sum_triangle(n): if n == 0: return 0 s = sum_triangle(n - 1) return s + n print(sum_triangle(1000))
出力
500500
スタックオーバーフロー
関数の再帰呼び出しでは、呼び出した関数が終わったときに計算を再開できるように、呼び出したときの関数の状態をメモリに保存する必要があります。したがって、関数の再帰が深くなればなるほど多くのメモリを消費します。このとき、メモリの スタック領域 と呼ばれる部分を消費します。スタック領域には上限があり、スタック領域を使い切ると RE が発生します。これを スタックオーバーフロー と言います。
多くの環境(あなたの手元の PC など)では、スタック領域は OS によりデフォルトで数 MB に制限されており、スタックオーバーフローに注意する必要があります。しかし、AtCoder ではこの制限が緩和されており、スタックオーバーフローで RE となるより先に、TLE や MLE となる可能性が高いです。
PyPy での再帰関数の最適化
PyPy は実行時にコンパイルを行なって処理を高速化しており、大抵の場合は CPython より高速ですが、関数呼び出しのインライン化(関数呼び出しを関数の中身に置き換えて関数呼び出しをなくすこと)に関わる処理と再帰関数の相性が悪く、PyPy で深い再帰を実行すると、CPython と比べて遅くなることがあります。
この場合には、再帰関数の実行前に pypyjit.set_param(inlining=0)
を実行し、インライン化をしないように設定することで高速化することができます。
設定を元に戻すには pypyjit.set_param("default")
を実行します。
詳しい情報は 公式ドキュメント を参照してください。
import pypyjit def sum_triangle(n): if n == 0: return 0 s = sum_triangle(n - 1) return s + n pypyjit.set_param(inlining=0) # 以下のコードではインライン化を行わない print(sum_triangle(1000)) pypyjit.set_param("default") # 以下のコードではインライン化を行う
さらなる再帰関数の例
(以下の例がよくわからない場合は、飛ばして問題ありません。)
クイックソート
再帰を使うことで、ソート関数を自分で実装することができます。
# 入力 : リスト a # 出力 : a を昇順にソートしたリスト def quick_sort(a): # ベースケース : a が空であれば、出力は空のリストである if len(a) == 0: return [] # a から要素を 1 つ取り出して p とする p = a.pop() # a から p 未満の要素を集めたリストを lo, p 以上の要素を集めたリストを hi とする lo = [] hi = [] for x in a: if x < p: lo.append(x) else: hi.append(x) # 再帰呼び出し : lo と hi をソートする lo = quick_sort(lo) hi = quick_sort(hi) # 答えの計算 : lo と p と hi をこの順に並べたものが a の昇順ソートである return lo + [p] + hi
この例では、再帰呼び出しのたびに a
の長さが 1 以上減少するので、必ずベースケースに到達します。
問題
リンク先の問題を解いてください。
Time Limit: 0 msec / Memory Limit: 0 KiB
キーポイント
- プログラムを実行するときには処理内容に応じた実行時間がかかる
- コンピュータの記憶領域(メモリ)は有限であり、プログラムで変数を使用した分だけメモリを消費する
- プログラムの実行時間・メモリ使用量が入力に応じてどのように変化するかを見積もったものを、それぞれ時間計算量・空間計算量という
- 計算量の表記方法としてオーダー記法が存在する
アルゴリズム
ある処理を行うプログラムを作成するときに、どのような計算を行っていくかという計算手順のことをアルゴリズムといいます。
例えば、1 から 100 まで整数の総和を計算するプログラムを考えます。1 + 2 + 3 + \cdots + 99 + 100 と順番に足していくというのは 1 つのアルゴリズムです。これをアルゴリズム A とします。一方、\frac{100 \times (1 + 100)}{2} という式を用いて計算するというのもアルゴリズムです。これをアルゴリズム B とします。
この例のように、プログラムを作成するにあたって複数のアルゴリズムが考えられることがあります。そのような場合には、どちらのアルゴリズムを用いるのかを選ぶことになります。
コンピュータでの計算は一回一回僅かとはいえ時間が掛かるので、計算回数が多くなるほど実行時間が長くなります。より計算回数が少ないアルゴリズムを選択することによって、高速に動作するプログラムを作成できます。
上の例でのアルゴリズム A は 99 回の足し算が必要ですが、アルゴリズム B では足し算・掛け算・割り算の計 3 回の計算で結果を求めることができます。99 回の計算が必要なアルゴリズム A と、3 回の計算が必要なアルゴリズム B、というように必要な計算の回数で大まかな性能を見積もることができます。
アルゴリズムの性能を比較する方法は色々ありますが、1 つの指標として計算量という考え方があります。
計算量
プログラムは入力に対して計算を行い、結果を出力します。このときに必要な計算時間や記憶領域の大きさが入力に対してどれくらい変化するか、ということを示す指標を計算量といいます。
計算量には時間計算量と空間計算量があります。単に計算量という場合、時間計算量を指すことが多いです。
時間計算量
「プログラムの計算のステップ数が入力に対してどのように変化するか」という指標を時間計算量といいます。計算のステップ数とは、四則演算や数値の比較などの単純な演算の回数です。
空間計算量
「プログラムが使用するメモリの量が入力に対してどのように変化するか」という指標を空間計算量といいます。メモリとはコンピュータの記憶領域のことを指し、変数を使用した分だけメモリを消費します。また、文字列や配列などは内部の要素数等に応じてメモリを消費します。
計算量の例
次のプログラムは 1 から N までの総和 (1 + 2 + 3 + \cdots + N) をループを用いて計算するものです。
N = int(input()) s = 0 for i in range(1, N + 1): s += i print(s)
このプログラムでは for 文で N 回の繰り返し処理を行っているので、計算ステップ数は入力の N の大小に応じて変わります。N 回の繰り返しを行うので、計算ステップ数はおおよそ N 回になります。このときの時間計算量は次で紹介するオーダー記法を用いて O(N) と表します。
このプログラムで使用している変数は、入力の N の大小に関わらず N
と s
と i
の 3 つです。 このときの空間計算量はオーダー記法を用いて O(1) と表します。
オーダー記法
厳密な計算ステップ数や必要な記憶領域の量は実装に用いるプログラミング言語や実装方法などによって異なるため、計算にかかる時間や記憶領域を厳密に見積もるのは非常に困難です。
このため、時間計算量や空間計算量の表現としてオーダー記法 O(\cdot) がよく用いられます。
例えば、3N^2 + 7N + 4 という式は、オーダー記法を用いて 3N^2 + 7N + 4 = O(N^2) などと表すことができます。これは、N が大きくなると、定数や詳細な係数を無視した場合にこの式が N^2 と同じ、またはそれよりも緩やかに増加することを意味します。
以下の手順によってオーダー記法による表記を得ることができます。
- ステップ 1: 係数を省略する。ただし定数については 1 とする。
- ステップ 2: N を大きくしたときに一番影響が大きい項を取り出し、O(\text{項}) と書く。
- 補足:N^2 + N + 1 という式の場合、「N^2」「N」「1」 それぞれを項といいます。
「一番影響が大きい項」というのは、基本的には N を大きくしていったときに「大きくなるスピードが最も速い項」と考えることができます。例えば N と N^2 を比較すると以下の表のようになるので、N^2 の方がより影響が大きいといえます。
N | N^2 | |
---|---|---|
N = 1 | 1 | 1 |
N = 10 | 10 | 100 |
N = 100 | 100 | 10{,}000 |
N = 1{,}000 | 1{,}000 | 1{,}000{,}000 |
3N^2 + 7N + 4 という式をオーダー記法で表す場合の手順は以下の通りです。
- ステップ 1: 係数を省略して N^2 + N + 1 とする。
- ステップ 2: N^2 + N + 1 で一番影響の大きい項は N^2 なので O(N^2) とする。
同じように 2N + 10 なら O(N) となります。
例題
次の式をそれぞれオーダー記法で表してください。
- N + 100
- 10N + N^3
- 3 + 5
- 2^N + N^2
- 補足:2^N = \underbrace{2 \times 2 \times \cdots \times 2}_{N \text{個}} であり、例えば 2^3 = 2 \times 2 \times 2 = 8 です。
答え
2^N と N^2 の比較は以下の通りです。クリックで答えを開く
2^N
N^2
N = 1
2
1
N = 3
8
9
N = 10
1{,}024
100
N = 30
1{,}073{,}741{,}824(約 10 億)
900
計算量(オーダー記法)の求め方
計算量を求めるには、計算ステップ数について式で表す必要があります。
次のプログラムについて、計算量がどうなるかを考えてみましょう。
N = int(input()) s = 0 for i in range(1, N + 1): for j in range(1, N + 1): s += i * j for i in range(1, N + 1): s += i for i in range(1, N + 1): s += i * i print(s)
1 つの二重ループと 2 つの一重ループがあるので、計算ステップはおおよそ N^2 + 2N ほどになります。よってこのプログラムの時間計算量は O(N^2) です。
このように、簡単なアルゴリズムであれば厳密な式を求めなくても「N 回の繰り返し処理があるから O(N)」や「1 から N+1 まで回す二重ループがあるから O(N^2)」などと見積もることができます。
例
いくつかの計算量オーダーについて、具体的なプログラムの例を挙げます。
O(1)
次のプログラムは、1 から N までの総和を公式を使って計算するものです。このプログラムの計算量は O(1) です。
""" 1 から N までの総和を公式を使って計算するプログラム 計算量:O(1) """ N = int(input()) s = N * (N + 1) // 2 print(s)
入力
10
実行結果
55
O(N)
次のプログラムは、要素数 N の配列の中に含まれる偶数の個数を数えるものです。このプログラムの計算量は O(N) です。
""" 要素数 N の配列の中に含まれる偶数の個数を数えるプログラム 計算量:O(N) """ N = int(input()) a = list(map(int, input().split())) cnt = 0 for x in a: if x % 2 == 0: cnt += 1 print(cnt)
入力
5 1 4 2 5 9
実行結果
2
O(N^2)
次のプログラムは、九九の要領で N \times N マスを埋めるものです。このプログラムの計算量は O(N^2) です。
""" 九九の要領で N × N マスを埋めるプログラム 計算量:O(N^2) """ N = int(input()) table = [[0 for j in range(N)] for i in range(N)] for i in range(N): for j in range(N): table[i][j] = (i + 1) * (j + 1) # N × N 回実行される for row in table: print(*row)
入力
9
実行結果
1 2 3 4 5 6 7 8 9 2 4 6 8 10 12 14 16 18 3 6 9 12 15 18 21 24 27 4 8 12 16 20 24 28 32 36 5 10 15 20 25 30 35 40 45 6 12 18 24 30 36 42 48 54 7 14 21 28 35 42 49 56 63 8 16 24 32 40 48 56 64 72 9 18 27 36 45 54 63 72 81
N \times N 要素の二次元配列を使っているので空間計算量も O(N^2) となります。
なお、上のプログラムは以下のように書くこともできます。以下の書き方でも時間計算量、空間計算量ともに元の書き方と変わらず O(N^2) となります。
""" 九九の要領で N × N マスを埋めるプログラム 計算量:O(N^2) """ N = int(input()) table = [[(i + 1) * (j + 1) for j in range(N)] for i in range(N)] for row in table: print(*row)
O(\log N)
まずはじめに簡単に \log について説明をします。
\log_{x} N という式は「x を何乗したら N になるか」を表します。例えば、2^4 = 16 なので、\log_{2} 16 = 4 です。
以下の図を見てください。長さ 8 の棒を長さが 1 になるまで半分に切る(2 で割る)ことを繰り返したときに切る回数は \log_{2} 8 = 3 回です。
このように計算量に出てくる \log は「半分にする回数」を表すことが多いです。
\log N は N に対して非常に小さくなるので、計算量の中に O(\log N) が出てきた場合でも実行時間にそこまで影響しないことが多いです。
オーダー記法では \log の底( \log_{2} 8 の _2 の部分)は省略して書かれることが多いです。
なぜなら、\log の底の違いは定数倍の違いだけとみなすことができ、オーダー記法では定数倍は省略するからです。
次のプログラムは N が 2 で何回割り切れるかを数えるものです。このプログラムの計算量は O(\log N) です。
上に挙げたイメージと同じような処理を行うプログラムなので、ループする回数が最大でも \log_{2} N 回になることが分かります。
""" N が 2 で何回割り切れるかを数えるプログラム 計算量:O(log N) """ N = int(input()) cnt = 0 while N % 2 == 0: cnt += 1 N //= 2 print(cnt)
計算量のおおまかな大小
主な計算量の大まかな大小は以下のようになります。
O(1) \lt O(\log N) \lt O(N) \lt O(N \log N) \lt O(N^2) \lt O(2^N)
時間計算量なら O(1) 側ほど高速に計算可能で、O(2^N) 側ほど時間がかかります。
これらの関係をグラフに示すと以下のようになります。
入力 N と実行時間のおおよその感覚との対応を次の表に示します。
N | O(1) | O(\log N) | O(N) | O(N \log N) | O(N^2) | O(2^N) |
---|---|---|---|---|---|---|
1 | 一瞬 | 一瞬 | 一瞬 | 一瞬 | 一瞬 | 一瞬 |
10 | 一瞬 | 一瞬 | 一瞬 | 一瞬 | 一瞬 | 一瞬 |
1{,}000 | 一瞬 | 一瞬 | 一瞬 | 一瞬 | 0.01 秒くらい | 地球爆発 |
10^6 | 一瞬 | 一瞬 | 0.01 秒くらい | 0.2 秒くらい | 3 時間くらい | 地球爆発 |
10^8 | 一瞬 | 一瞬 | 1 秒くらい | 30 秒くらい | 3 年くらい | 地球爆発 |
10^{16} | 一瞬 | 一瞬 | 3 年くらい | 170 年くらい | 地球爆発 | 地球爆発 |
1 秒くらいで計算が終わるようなプログラムを作りたいときは、入力の大きさの上限を見積もった上で 1 秒以内に収まるような計算量のアルゴリズムを選択する必要があります。
例えば、N = 10^6 くらいまでの入力で 1 秒以内に計算を終えるプログラムを作成するのであれば、O(N^2) のアルゴリズムでは間に合わない可能性が高いので、O(N) や O(N \log N) のアルゴリズムを用いて実装する必要があります。
AtCoder の問題では実行時間制約が 2 秒 ~ 5 秒くらいであることが多いです。コンテストに参加する人は、1 秒あたり 10^8 回くらいの計算ができることを覚えておきましょう。
複数の入力がある場合のオーダー記法
プログラムの入力が複数ある場合もオーダー記法で計算量を表すことができます。この場合それぞれの変数を残して書きます。例えば、2 つの入力 N と M を受け取る場合は O(N+M), O(NM), O((N+M)\log N) などのように書きます。
また、例えば N^2 M + NM + NM^2 という計算ステップ数になった場合は、計算量に影響する入力がひとつのときと同様に影響が一番大きくなりうる項のみを残すので O(N^2 M + NM^2) となります。
どの項を残すのかが分からなくても計算量の比較さえできればよいので問題ありません。複数の入力が計算量に影響することがあるということは頭に入れておきましょう。
細かい話
オーダー記法の落とし穴
計算量をオーダー記法で表記すると比較しやすくなりますが、オーダー記法は大まかな比較しかできない点に注意する必要があります。
オーダー記法では影響の一番大きな項のみを残して係数を省略して書きます。これによって、例えば計算ステップ数が 20N になるアルゴリズムと 2N になるアルゴリズムを比較しようとしたときに、実際には 10 倍もの差があるのに、オーダー記法ではどちらも O(N) となってしまいます。
同様の理由で、O(N) のアルゴリズム A と O(N \log N) のアルゴリズム B を比較するときに、計算量上はアルゴリズム A の方が高速でも、実際に動かしてみるとアルゴリズム B の方が高速だったということも起こり得ます。
正確に比較する必要がある場合は、実際に時間を計測して比較する必要があります。
組み込み関数の計算量
1.12.組み込み関数 で紹介した関数のうち、リストや文字列などを受け取る関数について、計算量は以下のようになります。
関数 | 計算量(引数のリスト等の長さを N とする) |
---|---|
len |
O(1)(以下に補足あり) |
sum |
O(N) |
sorted |
O(N \log N) |
len
関数についてlen
関数は、リストや文字列などの多くの組み込み型に対しては O(1) で動作します。これらの型では要素数が事前に管理されているため、単純な参照で済みます。しかし、カスタムクラスで __len__
メソッドを独自に定義している場合、その計算量は定義内容に依存します。
累乗計算を行う pow
関数についてpow(base, exp, mod)
関数では、指定方法によって計算量が変わります。
mod
引数を指定する場合(例: pow(2, 10, 100)
)は、モジュロ演算を使うため(mod
が非常に大きくなければ)計算量は O(\log(\mathrm{exp})) になります。mod
引数を指定しない場合(例: pow(2, 1000)
)は、大きな数の掛け算が必要になるため多倍長整数の乗算アルゴリズムに依存した計算量になります。
\log の性質
- O(\log 5N) \rightarrow O(\log N) (\log 5N = \log 5 + \log N なので)
- O(\log NM) \rightarrow O(\log N + \log M) (\log NM = \log N + \log M なので)
- どちらで書いてもよいですが、大きさを考えるときにこの関係を知っていると見積もりやすいかもしれません。
- O(\log N^2) \rightarrow O(\log N) (\log (N^2) = 2 \log N なので)
- O(\log 2^N) \rightarrow O(N) (\log (2^N) = N \log 2 なので)
問題
リンク先の問題を解いてください。
Time Limit: 0 msec / Memory Limit: 0 KiB
第2章修了
第2章お疲れ様でした。
2.01から2.06までの問題を全てACしたあなたは、実際に全ての「計算処理」を行うプログラムを書ける程の知識を身に着けました。
第3章について
現在準備中です。
学習コンテンツ紹介
AtCoderでは、自由に学習に活用できるコンテンツとして、AtCoderに慣れるための練習問題や、関連書籍の問題を実際に解くことができるコンテストを用意しています。
AtCoderをはじめたばかりの方には、ぜひこれらのコンテンツをご利用いただき、AtCoderの仕組みに慣れていただければ幸いです。
学習コンテンツ紹介
リンク先はAtCoderInfoサイト内にありますので、ご確認ください。
Time Limit: 0 msec / Memory Limit: 0 KiB
EX1「出力の練習 / 1.01」に戻る
EX5「A足すB問題 / 1.05」に戻る
コードテストの場所
練習問題を解くときは「コードテスト」というページを使ってプログラムを書き、正しく動作することが確認できたら提出するようにしましょう。
コードテストのページは、次の画像の赤い四角で囲った部分をクリックすることで開けます。
プログラムの実行
設定ができたら、「ソースコード」と書かれている場所にプログラムを書きます。
プログラムが書けたら「言語」が「Python(CPython 3.11.4)」または「Python (PyPy 3.10-v7.3.12)」になっていることを確認し、ページ下部にある「実行」ボタンを押してください。
実行結果
プログラムが実行できたら「標準出力」と書かれている場所にプログラムの出力が表示されます。
エラーが発生したとき
エラーが発生した場合は「標準エラー出力」と書かれている場所にエラーの詳細が表示されます。
たとえば開きカッコに対応する閉じカッコがない場合、SyntaxError となります。
print(123
標準エラー出力
File "Main.py", line 1 print(123 ^ SyntaxError: '(' was never closed
エラーの内容をヒントにプログラムを修正し、また実行してください。
エラーの直し方については1.02.プログラムの書き方とエラーで説明します。
EX1「出力の練習 / 1.01」に戻る
ここから先の説明はEX5「A足すB問題 / 1.05」を解く時に読んでください。
コードテストでの入力
コードテスト上で入力機能を使う場合、「標準入力」と書かれている場所に入力を書き、実行ボタンをクリックします。
Time Limit: 0 msec / Memory Limit: 0 KiB
EX2「エラーの修正 / 1.02」に戻る
EX5「A足すB問題 / 1.05」に戻る
AtCoder上でのエラーの表示
エラーの種類とAtCoder上の表示の対応は次の表の通りです。
エラーの種類 | ジャッジの判定 | 終了コード |
---|---|---|
コンパイルエラー | (Compile Error) | -1 |
実行時エラー | (Runtime Error) | 0以外 |
論理エラー | (Wrong Answer) | 0(エラーがない時と同じ) |
実行時間制限超過※ | (Time Limit Exceeded) | 9 |
「ジャッジの判定」は提出後の採点結果のことです。
「終了コード」はコードテスト上でプログラムを実行した後、画面の下部に表示されている数値のことです。
コードテスト上でコンパイルエラーが発生した場合、「標準エラー出力」にエラーの詳細が表示されます。
エラーの内容を読んだり、表示されているエラー内容を検索したりしてエラーを修正しましょう。
※実行時間制限超過がAPG4bPythonで発生する場合、提出先を間違えている可能性があります。説明ページではなく問題ページ(EX◯◯)に提出しているかを確認しましょう。
EX2「エラーの修正 / 1.02」に戻る
ここから先の説明はEX5「A足すB問題 / 1.05」を解く時に読んでください。
ACを取るには
入力を使う問題では、「ある入力ではACだったが、別の入力ではWAだった」という事がありえます。
例えば「A + Bを計算する問題」で、間違えてプログラムの出力をA - B
としてしまったとします。
この場合でも、入力がA = 5, B = 0だったとき、プログラムの出力と正解はどちらも5
となり一致し、この入力に対しては
となります。
しかし、入力がA = 2, B = 1だったとき、プログラムの出力は1
となりますが、正解は3
なので一致せず、この入力に対しては となります。
自分のプログラムの出力 | 正解 | 入力に対する採点結果 | |
---|---|---|---|
A = 5, B = 0 | 5 | 5 | |
A = 2, B = 1 | 1 | 3 |
問題の採点結果を
にするには、その問題の入力のパターン全てに対して正しい出力をする必要があります。提出の詳細の確認方法
採点結果が
だった場合は「提出の詳細」を見ると、いくつの入力例に対してACであり、いくつの入力例に対してWAであるかを見ることができます。この画面から実際にどのような入力が行われるかを確認することはできませんが、提出したプログラムがどれくらい正しいかのヒントにはなります。
提出の詳細を見るには、まず問題ページで「提出一覧」→「自分の提出」をクリックします。
すると自分の提出の一覧が表示されるので、見たい提出を選んで「詳細」をクリックすると、提出の詳細を見ることができます。
「提出一覧」→「すべての提出」をクリックすることで、他の人の提出を見ることもできます。
他の人のプログラムを見ることも勉強になるので、うまく活用してください。
Time Limit: 2 sec / Memory Limit: 1024 MiB
問題を解く前に 付録1.コードテストの使い方を読んでください。
問題文
次の出力をするプログラムを書いてください。
実行結果
こんにちは AtCoder
出力の最後(つまり AtCoder
の後)に改行する必要があります。注意してください。
回答プログラムの作成方法
回答プログラムは、次のサンプルプログラムを改変して作成することを推奨します。
プログラムをコピーし、コードテストのページに貼り付けましょう。
サンプルプログラム
print("Hello, world!") print("Hello, AtCoder!") print("Hello, Python!")
サンプルプログラムの実行結果
Hello, world! Hello, AtCoder! Hello, Python!
これを次の出力を行うようにプログラムを書き換え、正しく動作することが確認できたら提出しましょう。
正しい実行結果
こんにちは AtCoder
「AtCoder」の「C」は大文字であることに注意しましょう。
解答例
必ず自分で問題に挑戦してみてから見てください。
クリックで解答例を見る
print("こんにちは") print("AtCoder")
Time Limit: 2 sec / Memory Limit: 1024 MiB
問題を解く前に 付録2.提出結果の見方を読んでください。
問題文
A君は次の出力をするプログラムを作ろうとしました。
実行結果
いつも2525 AtCoderくん
しかし、書いたプログラムを実行してみるとエラーが発生しました。
A君が書いたプログラムのエラーを修正し、正しく動作するようにしてください。
Aくんが書いたプログラム
print("いつも2525" print(AtCoderくん)
このコードをコードテストで実行すると、標準エラー出力に以下のエラーメッセージが表示されます。
標準エラー出力
File "Main.py", line 1 print("いつも2525" ^ SyntaxError: '(' was never closed
この問題の取り組み方
Aくんのプログラムには複数のエラーが含まれています。
わかるエラーから一つずつ直し、コードテスト上で実行してみて、直っているかどうか確かめましょう。
コードテストの使い方は付録1.コードテストの使い方に書いてあります。
「AtCoder」の「C」は大文字であることに注意しましょう。
ヒント
1.02.プログラムの書き方とエラーの「エラーの例」を参考にしましょう。
解答例
必ず自分で問題に挑戦してみてから見てください。
クリックで解答例を見る
print("いつも2525") print("AtCoderくん")
Time Limit: 2 sec / Memory Limit: 1024 MiB
問題文
Aくんは1から100までの和を求めようと思いました。
数学の授業で習ったとおり、1から100までの和は次の式で求められます。
\dfrac{1}{2} \times 100 \times (100+1)
この式の値を出力してください。
ただし、答えを整数値で出力させるため 割り算に//
演算子を使って解いて下さい。 /
を使うとWAになります。
ヒント
「1.03.四則演算と優先順位」の「注意点」に書かれている項目を読んでみましょう。
コードテストで正しく動作することを確認してから提出しましょう。
解答例
必ず自分で問題に挑戦してみてから見てください。
クリックで解答例を見る
# // 2を最後に計算することで正しい計算結果になる print(100 * (100 + 1) // 2)
Time Limit: 2 sec / Memory Limit: 1024 MiB
問題文
次のプログラムをコピー&ペーストして、指定した場所にプログラムを追記することで問題を解いて下さい。
seconds = 365 * 24 * 60 * 60 print() # 1年は何秒か出力 print() # 2年は何秒か出力 print() # 5年は何秒か出力 print() # 10年は何秒か出力
int型の変数seconds
は一年の秒数を表しています。これを利用して
- 1年は何秒か
- 2年は何秒か
- 5年は何秒か
- 10年は何秒か
を順に一行ずつ表示するプログラムを作って下さい。
うるう秒やうるう年のことは考え無くて良いとします。
実行結果(一部を「?????」で隠しています)
31536000 63072000 ????? ?????
解答例
必ず自分で問題に挑戦してみてから見てください。
クリックで解答例を見る
seconds = 365 * 24 * 60 * 60 print(seconds) # 1年は何秒か出力 print(seconds * 2) # 2年は何秒か出力 print(seconds * 5) # 5年は何秒か出力 print(seconds * 10) # 10年は何秒か出力
Time Limit: 2 sec / Memory Limit: 1024 MiB
問題を解く前に、以下の2つのリンク先の説明を読んでください。
問題文
2つの整数A, Bが与えられます。A+Bの計算結果を出力してください。
制約
- 0 \le A, B \le 100
- A, B は整数
入力
入力は次の形式で標準入力から与えられます。
A B
出力
A+B の計算結果を出力してください。
ジャッジでは以下の入力例以外のケースに関してもテストされることに注意。
入力例1
1 2
出力例1
3
入力例2
100 99
出力例2
199
テスト入出力
書いたプログラムがACにならず、原因がどうしてもわからないときだけ見てください。
クリックでテスト入出力を見る
テスト入力1
0 0
テスト出力1
0
テスト入力2
0 50
テスト出力2
50
テスト入力3
0 50
テスト出力3
50
テスト入力4
27 81
テスト出力4
108
解答例
必ず自分で問題に挑戦してみてから見てください。
クリックで解答例を見る
A, B = map(int, input().split()) print(A + B)
Time Limit: 2 sec / Memory Limit: 1024 MiB
問題文
1行の計算式が与えられるので、その結果を出力してください。
与えられる計算式のパターンと対応する出力は以下の表の通りです。
入力 | 出力 | 備考 |
---|---|---|
A + B |
A + Bの計算結果を出力 | |
A - B |
A - Bの計算結果を出力 | |
A * B |
A \times Bの計算結果を出力 | |
A / B |
A \div Bの計算結果を出力 | 小数点以下は切り捨てて出力 B が 0 の場合はerror と出力 |
A ? B |
error と出力 |
|
A = B |
error と出力 |
|
A ! B |
error と出力 |
サンプルプログラム
このプログラムを元に解答を作成することを推奨します。
A, op, B = input().split() A = int(A) B = int(B) if op == "+": print(A + B) # ここにプログラムを追記
制約
- 0 \le A, B \le 100
- A, B は整数
- \mathrm{op} は
+
,-
,*
,/
,?
,=
,!
のいずれか一つ
入力
入力は次の形式で標準入力から与えられます。
A \mathrm{op} B
出力
入力の計算式の計算結果を出力してください。
ジャッジでは以下の入力例以外のケースに関してもテストされることに注意。
入力例1
1 + 2
出力例1
3
入力例2
5 - 3
出力例2
2
入力例3
10 * 20
出力例3
200
入力例4
10 / 3
出力例4
3
計算結果の小数点以下は切り捨てます。
入力例5
100 / 0
出力例5
error
B が 0 の場合は error
と出力することに注意してください。
入力例6
25 ? 31
出力例6
error
入力例7
0 + 0
出力例7
0
テスト入出力
書いたプログラムがACにならず、原因がどうしてもわからないときだけ見てください。
クリックでテスト入出力を見る
テスト入力1
100 = 100
テスト出力1
error
テスト入力2
17 ! 91
テスト出力2
error
テスト入力3
0 / 20
テスト出力3
0
テスト入力4
0 * 0
テスト出力4
0
テスト入力5
0 - 0
テスト出力5
0
解答例
必ず自分で問題に挑戦してみてから見てください。
クリックで解答例を見る
A, op, B = input().split() A = int(A) B = int(B) if op == "+": print(A + B) elif op == "-": print(A - B) elif op == "*": print(A * B) elif op == "/" and B != 0: print(A // B) else: print("error")
Time Limit: 2 sec / Memory Limit: 1024 MiB
問題文
次のプログラムで宣言されている bool 型の変数 a
, b
, c
に対し、True
または False
を代入することで、AtCoder
と出力されるようにしてください。
1, 2, 3 行目における変数 a
,b
,c
への代入以外のプログラムの書き換えは行わないものとします。
プログラム
a = # True または False b = # True または False c = # True または False # ここから先は変更しないこと assert (a is True) or (a is False) assert (b is True) or (b is False) assert (c is True) or (c is False) if a: print("At", end="") else: print("Yo", end="") if not a and b: print("Bo", end="") else: print("Co", end="") if a and b and c: print("foo!", end="") elif True and False: print("year!", end="") elif not a or c: print("der", end="") print("")
入力
この問題に入力はありません
出力
AtCoder
と出力してください。
回答例
答え方の例です。
変数 a
, b
, c
の全てに False
を代入していますが、YoCoder
と出力されているので、この解答は不正解となります。
a = False # True または False b = False # True または False c = False # True または False # ここから先は変更しないこと assert (a is True) or (a is False) assert (b is True) or (b is False) assert (c is True) or (c is False) if a: print("At", end="") else: print("Yo", end="") if not a and b: print("Bo", end="") else: print("Co", end="") if a and b and c: print("foo!", end="") elif True and False: print("year!", end="") elif not a or c: print("der", end="") print("")
出力例
YoCoder
解答例
必ず自分で問題に挑戦してみてから見てください。
クリックで解答例を見る
a = True # True または False b = False # True または False c = True # True または False # ここから先は変更しないこと assert (a is True) or (a is False) assert (b is True) or (b is False) assert (c is True) or (c is False) if a: print("At", end="") else: print("Yo", end="") if not a and b: print("Bo", end="") else: print("Co", end="") if a and b and c: print("foo!", end="") elif True and False: print("year!", end="") elif not a or c: print("der", end="") print("")
Time Limit: 2 sec / Memory Limit: 1024 MiB
問題文
以下の問題を N 回解いてください。
2 つの整数 A, B が与えられます。A+B の計算結果を出力してください。
制約
- 1 \le N \le 10
- 0 \le A_i, B_i \le 100
- N, A_i, B_i は整数
入力
入力は次の形式で標準入力から与えられます。
N A_1 B_1 A_2 B_2 \vdots A_N B_N
出力
N 行出力してください。
i 行目には、A_i+B_i の計算結果を出力してください。
ジャッジでは以下の入力例以外のケースに関してもテストされることに注意。
入力例1
1 42 37
出力例1
79
入力例2
4 3 1 4 1 5 9 2 6
出力例2
4 5 14 8
ヒント
クリックでヒントを見る
この問題は EX5.A足すB問題 と同じ処理を N 回繰り返すことで解くことができます。また、「N 回繰り返す」という操作は while
文を用いて書くことができます。
テスト入出力
書いたプログラムがACにならず、原因がどうしてもわからないときだけ見てください。
クリックでテスト入出力を見る
テスト入力1
1 79 68
テスト出力1
147
テスト入力2
4 90 46 73 74 93 21 99 42
テスト出力2
136 147 114 141
テスト入力3
5 49 81 46 39 50 89 26 97 84 46
テスト出力3
130 85 139 123 130
テスト入力4
8 13 54 81 51 8 64 89 85 44 51 51 94 39 84 2 45
テスト出力4
67 132 72 174 95 145 123 47
テスト入力5
10 76 15 58 31 21 40 86 88 77 58 77 33 25 70 8 81 67 79 73 99
テスト出力5
91 89 61 174 135 110 95 89 146 172
解答例
必ず自分で問題に挑戦してみてから見てください。
クリックで解答例を見る
N = int(input()) i = 0 while i < N: A, B = map(int, input().split()) print(A + B) i += 1
Time Limit: 2 sec / Memory Limit: 1024 MiB
問題文
電卓の操作が与えられるので、計算途中の値と計算結果の値を出力してください。
電卓の操作は次の形式で入力されます。
入力の形式
計算の数N 最初の値A 演算子\mathrm{op}_1 計算する値B_1 演算子\mathrm{op}_2 計算する値B_2 \vdots 演算子\mathrm{op}_N 計算する値B_N
次の入力は ((2 + 1) \times 3) \div 2 を表しています。
入力例
3 2 + 1 * 3 / 2
出力では「何行目の出力か」と、「計算途中の値」を出力してください。
出力の形式
1 1個目の計算結果 2 2個目の計算結果 \vdots N N個目の計算結果
次の出力は上の入力例に対する出力です。
出力例
1 3 2 9 3 4
与えられる入力のパターンと対応する処理は以下の表の通りです。
入力 | 処理 | 備考 |
---|---|---|
+ B |
それまでの計算結果に B を足し、その値を出力する。 | |
- B |
それまでの計算結果から B を引き、その値を出力する。 | |
* B |
それまでの計算結果に B を掛け、その値を出力する。 | |
/ B |
それまでの計算結果を B で割り、その値を出力する。 | 小数点以下は切り捨てて計算を行う。 B が 0 の場合はerror と出力し、それ以降は出力を行わない。 |
/ B
において、B が 0 の場合は error
と出力し、それ以降は出力を行わない ことに注意してください。
ページ末尾に問題のヒントがあります。詰まったら見てみましょう。
サンプルプログラム
N = int(input()) A = int(input()) # ここにプログラムを追記
制約
- 1 \le N \le 7
- 0 \le A, B_i \le 10
- A, B_i, N は整数
- \mathrm{op}_i は
+
,-
,*
,/
のいずれか
入力
入力は次の形式で標準入力から与えられます。
N A \mathrm{op}_1 B_1 \mathrm{op}_2 B_2 \vdots \mathrm{op}_N B_N
出力
1 1個目の計算結果 2 2個目の計算結果 \vdots N N個目の計算結果
ジャッジでは以下の入力例以外のケースに関してもテストされることに注意。
入力例1
3 2 + 1 * 3 / 2
出力例1
1 3 2 9 3 4
問題文中で説明した入出力です。
入力例2
2 3 / 2 / 2
出力例2
1 1 2 0
割り算では小数点以下を切り捨てます。
入力例3
4 3 + 1 / 0 * 2 - 10
出力例3
1 4 error
割り算で B_i が 0 の場合は error
と出力し、それ以降は出力をしないことに注意してください。
入力例4
7 10 * 10 * 10 * 10 * 10 * 10 * 10 * 10
出力例4
1 100 2 1000 3 10000 4 100000 5 1000000 6 10000000 7 100000000
ヒント1
次のプログラムは、5つの整数を入力で一つずつ受け取り、受け取った整数を足して出力します。
また、今回の問題と同様に、出力が何行目かも同時に出力します。
今回の問題を解く際の参考にしてください。
クリックでヒントプログラムを見る
total = 0 for i in range(5): x = int(input()) total += x # 行番号 合計値 の形式で出力 print(i + 1, total)
ヒント入力
3 4 2 1 10
ヒント出力
1 3 2 7 3 9 4 10 5 20
ヒント2
ジャッジでテストされる入力のうち2つをヒントとして示します。
サンプルは全てあっていてWAの原因がわからない人は、以下のケースに正解しているか確かめてみましょう。
クリックでテストケースを見る
テスト入力1
7 0 - 5 * 7 / 4 + 0 + 0 - 10 * 5
テスト出力1
1 -5 2 -35 3 -9 4 -9 5 -9 6 -19 7 -95
テスト入力2
6 0 / 4 / 3 / 5 + 3 / 7 / 1
テスト出力2
1 0 2 0 3 0 4 3 5 0 6 0
テスト入出力
書いたプログラムがACにならず、原因がどうしてもわからないときだけ見てください。
クリックでテスト入出力を見る
テスト入力1
7 0 - 5 * 7 / 4 + 0 + 0 - 10 * 5
テスト出力1
1 -5 2 -35 3 -9 4 -9 5 -9 6 -19 7 -95
テスト入力2
6 0 / 4 / 3 / 5 + 3 / 7 / 1
テスト出力2
1 0 2 0 3 0 4 3 5 0 6 0
テスト入力3
3 1 * 4 * 3 / 0
テスト出力3
1 4 2 12 error
テスト入力4
7 0 * 2 / 0 + 1 / 0 + 5 * 2 - 10
テスト出力4
1 0 error
解答例
必ず自分で問題に挑戦してみてから見てください。
クリックで解答例を見る
N = int(input()) A = int(input()) for i in range(N): op, x = input().split() x = int(x) if op == "+": A += x elif op == "-": A -= x elif op == "*": A *= x elif op == "/" and x != 0: A //= x else: print("error") break print(i + 1, A)
Time Limit: 2 sec / Memory Limit: 1024 MiB
問題文
N 人のテストの点数が与えられます。
それぞれの人の点数が平均点から何点離れているか計算してください。
平均点は次の式で求められます。i 番目の人の点数は A_i で表します。
平均点 = \dfrac{A_1 + A_2 + \cdots + A_N}{N}
平均点が整数にならない場合、小数点以下を切り捨てた数値を平均点とします。
例えば 3 人のテストの点数が「2 点、1 点、4 点」だったとき、\frac{2 + 1 + 4}{3} = 2.333 \cdotsなので、平均点は小数点以下を切り捨てて 2 点になります。
そして、平均点から何点離れているかを計算した結果である「0 点、1 点、2 点」が答えになります。
「0 点、-1 点、2 点」でも「0 点、1 点、-2 点」でも無いことに注意してください。
ページ末尾に問題のヒントがあります。詰まったら見てみましょう。
サンプルプログラム
N = int(input()) # ここにプログラムを追記
制約
- 1 \le N \le 1000
- 0 \le A_i \le 100
- N, A_i は整数
入力
入力は次の形式で標準入力から与えられます。
N A_1 A_2 \cdots A_N
出力
それぞれの人の点数が平均点から何点離れているかを1行ずつ出力してください。
1人目の計算結果 2人目の計算結果 \vdots N人目の計算結果
ジャッジでは以下の入力例以外のケースに関してもテストされることに注意。
入力例1
3 2 1 4
出力例1
0 1 2
問題文中で説明した入出力です。
入力例2
2 80 70
出力例2
5 5
このケースの平均点は 75 点です。
どちらの点数も平均点から 5 点離れています。
入力例3
5 100 100 100 100 100
出力例3
0 0 0 0 0
このケースの平均点は 100 点です。
入力例4
10 53 21 99 83 75 40 33 62 18 100
出力例4
5 37 41 25 17 18 25 4 40 42
ヒント
今回の問題は次のプログラムに追記すれば解くことができます。
追記箇所が2つあることに注意してください。
クリックでヒントプログラムを見る
N = int(input()) # スペース区切りの整数をリストとして受け取る A = list(map(int, input().split())) # 合計点 total = 0 # 合計点を計算 for i in range(N): # ①ここにプログラムを追記 # 平均点 (Nで割って切り捨てた値) mean = total // N # 平均点から何点離れているかを計算して出力 for i in range(N): # ②ここにプログラムを追記 # 負の数を出力しないように注意
テスト入出力
書いたプログラムがACにならず、原因がどうしてもわからないときだけ見てください。
クリックでテスト入出力を見る
テスト入力1
1 80
テスト出力1
0
テスト入力2
データが大きすぎるため省略
解答例
必ず自分で問題に挑戦してみてから見てください。
クリックで解答例を見る
N = int(input()) # スペース区切りの整数をリストとして受け取る A = list(map(int, input().split())) # 合計点 total = 0 # 合計点を計算 for i in range(N): # ①ここにプログラムを追記 total += A[i] # 平均点 (Nで割って切り捨てた値) mean = total // N # 平均点から何点離れているかを計算して出力 for i in range(N): # ②ここにプログラムを追記 # 負の数を出力しないように注意 if A[i] > mean: print(A[i] - mean) else: print(mean - A[i])
Time Limit: 2 sec / Memory Limit: 1024 MiB
問題文
1
と+
と-
のみからなる式 S が 1 行で与えられるので、計算結果を出力してください。
例えば式 S が 1+1+1-1
であったとき、計算結果は 2 です。
具体的な式 S の形式は以下の通りです。
- 式 S の 1 文字目は必ず
1
です。 - その後、「
+
または-
」と1
が交互に続きます。 - S の最後の文字も必ず
1
です。
式と演算子はスペースで区切られていないことに注意してください。
ページ末尾に問題のヒントがあります。詰まったら見てみましょう。
サンプルプログラム
S = input() # ここにプログラムを追記
制約
- 1 \le |S| \le 100 (|S| は文字列の長さ)
- S は
1
から始まり、その後「+
または-
」と1
が交互に続き、1
で終わる
入力
入力は次の形式で標準入力から与えられます。
S
出力
Sの計算結果を出力してください。
ジャッジでは以下の入力例以外のケースに関してもテストされることに注意。
入力例1
1+1+1-1
出力例1
2
問題文中で説明した入出力です。
入力例2
1-1-1-1-1-1
出力例2
-4
計算結果は負の値になることもあります。
入力例3
1
出力例3
1
入力は 1
だけで終わることもあります。
入力例4
1-1-1+1+1+1+1-1+1-1+1-1+1
出力例4
3
ヒント
次のプログラムは 9 文字の式 S にいくつ 1
が含まれているかを出力するプログラムです。
今回の問題を解く際の参考にしてください。
クリックでヒントプログラムを見る
S = input() count = 0 # 9 文字の式に限定していることに注意 for i in range(9): if S[i] == "1": count += 1 print(count)
ヒント入力
1-1-1+1+1
ヒント出力
5
テスト入出力
書いたプログラムがACにならず、原因がどうしてもわからないときだけ見てください。
クリックでテスト入出力を見る
テスト入力1
1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1+1
テスト出力1
-25
テスト入力2
1+1+1+1+1+1+1+1+1+1+1+1+1
テスト出力2
13
テスト入力3
1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1
テスト出力3
-33
テスト入力4
1+1-1+1-1+1-1+1-1+1-1+1-1+1-1+1+1-1-1+1+1-1-1+1+1+1-1-1-1+1
テスト出力4
2
解答例
必ず自分で問題に挑戦してみてから見てください。
クリックで解答例を見る
S = input() # 計算結果を保持する変数 answer = 1 for i in range(len(S)): if S[i] == "+": answer += 1 if S[i] == "-": answer -= 1 print(answer)
in
の後ろに文字列を指定することで、文字に対して繰り返し処理を行うこともできます。
S = input() # 計算結果を保持する変数 answer = 1 for c in S: if c == "+": answer += 1 if c == "-": answer -= 1 print(answer)
Time Limit: 2 sec / Memory Limit: 1024 MiB
問題文
学校の授業で持久走がありました。N 人の生徒がゴールまでにかかった時間(秒数)がそれぞれ T_1, T_2, \dots , T_N で与えられます。一番早くゴールしたのは何番目の生徒でしょうか。
ページ末尾に問題のヒントがあります。詰まったら見てみましょう。
サンプルプログラム
N = int(input()) # 生徒の数Nを読み込む T = list(map(int, input().split())) # 各生徒のゴールまでの時間を読み込む # ここにプログラムを追記
制約
- 1 \leq N \leq 100
- 100 \leq T_i \leq 1000 (全ての i について)
- 全ての生徒のゴールまでにかかった時間は異なる
入力
入力は次の形式で標準入力から与えられます。
N T_1 T_2 ... T_N
出力
一番早くゴールした生徒の番号を出力してください。
ジャッジでは以下の入力例以外のケースに関してもテストされることに注意。
入力例1
5 300 450 200 400 350
出力例1
3
3 番目の生徒の 200 秒が最速のタイムになります。
入力例2
1 400
出力例2
1
1 人で走るときは常に最速ランナーになることができます。
入力例3
10 883 940 579 641 979 999 331 842 667 518
出力例3
7
ヒント
クリックでヒントを見る
二段階のステップで考えてみましょう。問題を解くために必要な情報は 2 つあります。
1.最小値が何か
2.最小値が何番目に現れるか
一度に求めようとせずに、1. と 2. を順番に調べるコードを書いてみましょう。
テスト入出力
書いたプログラムがACにならず、原因がどうしてもわからないときだけ見てください。
クリックでテスト入出力を見る
テスト入力1
100 794 341 925 159 319 451 671 720 836 667 664 917 114 112 276 479 623 796 152 910 566 996 656 567 756 105 139 172 694 771 148 906 164 983 852 419 863 357 895 732 608 398 227 165 785 532 613 136 304 198 303 475 434 502 189 477 588 439 969 686 520 168 967 344 972 828 694 786 256 338 493 721 390 874 632 888 702 911 550 971 802 427 646 443 487 594 826 949 245 834 596 674 810 526 395 541 227 665 948 723
テスト出力1
26
テスト入力2
100 171 981 971 462 134 134 174 115 445 664 109 418 836 310 163 198 474 825 200 903 788 933 127 193 781 859 719 215 558 454 306 664 359 692 424 417 337 634 772 343 265 126 159 847 981 577 691 601 713 464 654 572 559 339 177 497 420 112 475 357 297 152 547 382 770 871 787 502 477 138 276 443 349 374 279 873 837 762 769 573 479 216 186 307 798 140 820 976 473 825 606 580 342 370 456 171 266 730 748 810
テスト出力2
11
テスト入力3
100 390 507 538 277 832 570 897 460 638 456 708 466 228 442 672 107 557 599 320 578 979 239 792 541 776 377 878 124 847 262 563 246 369 115 258 380 472 899 139 913 323 637 353 200 804 320 727 687 457 499 206 752 815 522 416 746 964 804 834 548 821 689 981 101 428 862 969 935 173 941 104 891 954 267 466 424 398 231 227 320 488 931 904 494 955 663 499 558 728 396 741 446 219 826 782 893 398 113 904 683
テスト出力3
64
解答例
必ず自分で問題に挑戦してみてから見てください。
クリックで解答例を見る
N = int(input()) # 生徒の数Nを読み込む T = list(map(int, input().split())) # 各生徒のゴールまでの時間を読み込む T_min = min(T) for i, v in enumerate(T): if v == T_min: print(i + 1) break
Time Limit: 2 sec / Memory Limit: 1024 MiB
問題文
三人兄弟の A 君と B 君と C 君は、お父さんに 1 つのプレゼントを貰うことになりました。
貰えるプレゼントの予算は「テストの合計点の積」で決まります。
三人兄弟はそれぞれ N 個のテストを受けました。
A 君と B 君と C 君の「i 番目のテストの点数」をそれぞれ A_i, B_i, C_i で表すと、プレゼントの予算は次の式で求まります。
プレゼントの予算 = (A_1 + A_2 + \cdots + A_N) \times (B_1 + B_2 + \cdots + B_N) \times (C_1 + C_2 + \cdots + C_N)
例えば、2 個のテスト受けた結果、A 君は 5 点と 7 点、B 君は 4 点と 10 点、C 君は 9 点と 2 点だったとします。
この場合、(5 + 7) \times (4 + 10) \times (9 + 2) = 12 \times 14 \times 11 = 1848 より、プレゼントの予算は 1848 円になります。
A 君はこの計算を行うプログラムを途中まで書きました。
A 君が書いたプログラムに追記し、プログラムを完成させてください。
ページ末尾に問題のヒントがあります。詰まったら見てみましょう。
A君が書いたプログラム
def sum_scores(scores): """ 1 人のテストの点数を表すリストから合計点を計算して返す関数 引数 scores: scores[i] に i 番目のテストの点数が入っている 返り値: 1 人のテストの合計点 """ # ここにプログラムを追記 def output(sum_a, sum_b, sum_c): """ 3 人の合計点からプレゼントの予算を計算して出力する関数 引数 sum_a: A 君のテストの合計点 引数 sum_b: B 君のテストの合計点 引数 sum_c: C 君のテストの合計点 返り値: なし """ # ここにプログラムを追記 # ------------------- # ここから先は変更しない # ------------------- def input_list(N): """ N 個の入力を受け取ってリストに入れて返す関数 引数 N: 入力を受け取る個数 返り値: 受け取った N 個の整数値からなるリスト """ l = list(map(int, input().split())) return l # 科目の数 N を受け取る N = int(input()) # それぞれのテストの点数を受け取る A = input_list(N) B = input_list(N) C = input_list(N) # それぞれの合計点を計算 sum_A = sum_scores(A) sum_B = sum_scores(B) sum_C = sum_scores(C) # プレゼントの予算を出力 output(sum_A, sum_B, sum_C)
制約
- 1 \le N \le 10
- 0 \le A_i, B_i, C_i \le 100
- N, A_i, B_i, C_i は整数
入力
入力は次の形式で標準入力から与えられます。
N A_1 A_2 \cdots A_N B_1 B_2 \cdots B_N C_1 C_2 \cdots C_N
出力
プレゼントの予算を出力してください。
ジャッジでは以下の入力例以外のケースに関してもテストされることに注意。
入力例1
2 5 7 4 10 9 2
出力例1
1848
問題文で説明したケースです。
入力例2
3 100 100 100 100 100 100 100 100 100
出力例2
27000000
300 \times 300 \times 300 = 27000000 なので、三人兄弟は 27000000 円分のプレゼントを貰えることになりました。
入力例3
5 95 20 74 81 10 100 50 32 84 31 0 0 0 0 0
出力例3
0
C 君の合計点が 0 点だったので、プレゼントの予算も 0 円になります。
入力例4
2 10 0 0 5 1 1
出力例4
100
ヒント1
クリックでヒントを見る
今回の問題は今までの問題とは少し異なり、用意されたプログラムの動作を理解し、意図を読み取る必要があります。
プログラムがどのような順番で実行されていくかに注意して、A 君が書いたプログラムを読んでみましょう。
ヒント2
この問題は「A 君が書いたプログラムに追記して完成させる」という問題ですが、ヒントとして関数を使わずにこの問題と同じ処理をするプログラムを示します。このプログラムを参考にして A 君の sum_scores 関数と output 関数を完成させてください。
クリックでヒントプログラムを見る
# 科目の数 N を受け取る N = int(input()) # それぞれのテストの点数を受け取る # N 個の入力をそれぞれ受け取り、リスト A, B, C とする A = list(map(int, input().split())) B = list(map(int, input().split())) C = list(map(int, input().split())) # 以下はプレゼントの予算を出力する処理 # テストの点数を表すリストからそれぞれの合計点を計算 sum_a = sum(A) sum_b = sum(B) sum_c = sum(C) # 3 人の合計点からプレゼントの予算を計算して出力する print(sum_a * sum_b * sum_c)
テスト入出力
書いたプログラムがACにならず、原因がどうしてもわからないときだけ見てください。
クリックでテスト入出力を見る
テスト入力1
10 2 8 3 1 10 8 32 15 9 100 5 1 2 0 3 2 1 10 43 20 0 100 7 10 0 82 19 0 90 51
テスト出力1
5871804
解答例
必ず自分で問題に挑戦してみてから見てください。
クリックで解答例を見る
def sum_scores(scores): """ 1 人のテストの点数を表すリストから合計点を計算して返す関数 引数 scores: scores[i] に i 番目のテストの点数が入っている 返り値: 1 人のテストの合計点 """ s = sum(scores) return s def output(sum_a, sum_b, sum_c): """ 3 人の合計点からプレゼントの予算を計算して出力する関数 引数 sum_a: A 君のテストの合計点 引数 sum_b: B 君のテストの合計点 引数 sum_c: C 君のテストの合計点 返り値: なし """ print(sum_a * sum_b * sum_c) # ------------------- # ここから先は変更しない # ------------------- def input_list(N): """ N 個の入力を受け取ってリストに入れて返す関数 引数 N: 入力を受け取る個数 返り値: 受け取った N 個の整数値からなるリスト """ l = list(map(int, input().split())) return l # 科目の数 N を受け取る N = int(input()) # それぞれのテストの点数を受け取る A = input_list(N) B = input_list(N) C = input_list(N) # それぞれの合計点を計算 sum_A = sum_scores(A) sum_B = sum_scores(B) sum_C = sum_scores(C) # プレゼントの予算を出力 output(sum_A, sum_B, sum_C)
Time Limit: 2 sec / Memory Limit: 1024 MiB
問題文
5つの要素からなる配列が与えられます。
同じ値の要素が隣り合っているような箇所が存在するかどうかを判定してください。
存在するなら"YES"を、存在しなければ"NO"を出力してください。
この問題も以下の手順で解いてみましょう。
- ループを使わないで書く
- パターンを見つける
- ループで書き直す
data = [int(c) for c in input().split()] # dataの中で隣り合う等しい要素が存在するなら"YES"を出力し、そうでなければ"NO"を出力する # ここにプログラムを追記
制約
- 0≦A_i≦100 (1 ≦ i ≦ 5)
- A_i (1 ≦ i ≦ 5)は整数
入力
入力は次の形式で標準入力から与えられます。
A_1 A_2 A_3 A_4 A_5
出力
配列の隣り合う要素のうち、値が等しいものが存在するなら"YES"を、存在しなければ"NO"を出力してください。
出力の最後には改行が必要です。
ジャッジでは以下の入力例以外のケースに関してもテストされることに注意。
入力例1
5 3 3 1 4
出力例1
YES
入力例2
1 1 2 3 4
出力例2
YES
入力例3
1 2 1 2 1
出力例3
NO
入力例4
100 100 100 100 100
出力例4
YES
ヒント
- ループを使わないで書く
- パターンを見つける
- ループで書き直す
に沿って書く場合の流れをヒントで説明します。
まずは自分で考えてみて、詰まったら見てみましょう。
ヒント1 (ループを使わないで書く)
隣合っている等しい要素があるかを順番に確認します。クリックでヒントプログラムを見る
# dataの中で隣り合う等しい要素が存在するなら"YES"を出力し、そうでなければ"NO"を出力する
if data[0]==data[1] or data[1]==data[2] or data[2]==data[3] or data[3]==data[4]:
print("YES")
else:
print("NO")
ヒント2 (パターンを見つける)
繰り返しのパターンを見つけます。 このままだと直接for文に書き換えにくいので,「または」の条件を複数のif文に分解します。 次のパターンが繰り返し存在していることが分かります。クリックでヒントを見る
ヒント1のヒントプログラムでは2行目のif文の中身が同じパターンの繰り返しになっています。if data[0]==data[1] or data[1]==data[2] or data[2]==data[3] or data[3]==data[4]:
また、答えがYESなのかNOなのかをbool型の変数に入れるようにします。次のプログラムではYESならTrue、NOならFalseとしています。ans = False # 始めはfalseにしておき、条件を満たすときにtrueになるようにする
if data[0]==data[1]:
ans = True
if data[1]==data[2]:
ans = True
if data[2]==data[3]:
ans = True
if data[3]==data[4]:
ans = True
if ans:
print("YES")
else:
print("NO")
if data[k]==data[k+1]:
ans = True
ヒント3 (ループで書き直す)
以下のように for 文を書いてみてください:クリックでヒントを見る
ans = False
for k in (#ここを埋めてください):
if data[k]==data[i+1]:
ans = True
テスト入出力
書いたプログラムがACにならず、原因がどうしてもわからないときだけ見てください。
クリックでテスト入出力を見る
テスト入力1
0 1 2 3 3
テスト出力1
YES
テスト入力2
0 1 100 100 4
テスト出力2
YES
テスト入力3
40 30 66 44 65
テスト出力3
NO
解答例
必ず自分で問題に挑戦してみてから見てください。
クリックで解答例を見る
data = [int(c) for c in input().split()]
# dataの中で隣り合う等しい要素が存在するなら"YES"を出力し、そうでなければ"NO"を出力する
ans = False # 始めはfalseにしておき、条件を満たすときにtrueになるようにする
for k in range(len(data)-1):
if data[k]==data[k+1]:
ans = True
if ans:
print("YES")
else:
print("NO")
Time Limit: 2 sec / Memory Limit: 1024 MiB
問題文
ある果物屋では、リンゴ・パイナップルがそれぞれN個売られています。i個目のリンゴ、パイナップルはそれぞれA_i円、P_i円です。
リンゴ・パイナップルをそれぞれ1つずつ選んで購入しようとするとき、合計金額が丁度S円になるような買い方が何通りあるか求めてください。
ただし、同じ値段の同じ種類の商品でも区別します。たとえば同じ値段のリンゴが複数あった場合、それらを買う場合は別の買い方として数えます。パイナップルについても同様です。また、リンゴ・パイナップルを買う順番は考慮しないものとします。
制約
- 0\le N\le 100
- 0\le S\le 1000
- 1\le A_i, P_i\le 500\ (1 \le i \le N)
- 入力される値はすべて整数である
入力
入力は次の形式で標準入力から与えられます。
N S A_1 A_2 \cdots A_N P_1 P_2 \cdots P_N
出力
リンゴ・パイナップルをそれぞれ1つずつ購入しようとするとき、合計金額が丁度S円になるような買い方が何通りあるかを出力してください。
サンプルプログラム
入力を受け取るサンプルプログラムを以下に示します。
N, S = map(int, input().split()) A = list(map(int, input().split())) B = list(map(int, input().split())) # リンゴ・パイナップルをそれぞれ1つずつ購入するとき合計S円になるような買い方が何通りあるか # ここにプログラムを追記
ジャッジでは以下の入力例以外のケースに関してもテストされることに注意。
入力例1
3 400 100 100 130 270 300 250
出力例1
3
合計が400円になる買い方は以下の3通りです。
1つ目のリンゴ、2つ目のパイナップルを購入すると、100円 + 300円 = 400円となります。
2つ目のリンゴ、2つ目のパイナップルを購入すると、100円 + 300円 = 400円となります。
3つ目のリンゴ、1つ目のパイナップルを購入すると、130円 + 270円 = 400円となります。
1つ目のリンゴと2つ目のリンゴがどちらも100円ですが、別の買い方として数えることに注意してください。
入力例2
10 600 70 110 90 120 90 20 260 150 220 150 170 100 250 350 60 280 450 460 20 220
出力例2
2
入力例3
3 200 100 100 100 100 100 100
出力例3
9
入力例4
1 0 100 200
出力例4
0
合計0円で買うことはできません。
ヒント
- ループを使わないで書く
- パターンを見つける
- ループで書き直す
に沿って書く場合の流れをヒントで説明します。
まずは自分で考えてみて、詰まったら見てみましょう。
ヒント1 (ループを使わないで書く)
合計がS円になるような買い方が何通りあるかを求めるので、 一般のNでは考えにくいので、試してみるときはNを小さい値で仮定してみるとよいです。 添え字の変化に着目するとパターン化できそうです。クリックでヒントを見る
すべての組み合わせをif文で確認できれば答えが求まるはずです。
N = 3のケースを全て書き出してみます。(添え字として0始まりの値を使っている点に注意してください。)ans = 0
if A[0] + P[0] == S:
ans += 1
if A[0] + P[1] == S:
ans += 1
if A[0] + P[2] == S:
ans += 1
if A[1] + P[0] == S:
ans += 1
if A[1] + P[1] == S:
ans += 1
if A[1] + P[2] == S:
ans += 1
if A[2] + P[0] == S:
ans += 1
if A[2] + P[1] == S:
ans += 1
if A[2] + P[2] == S:
ans += 1
ヒント2 (パターンを見つける)
変数i, jを導入してi番目のリンゴ、j番目のパイナップルを買う場合を考えます。 あとはi, jを網羅的に動かして上の文を実行できればよいです。クリックでヒントを見る
条件式は次のようになります。if A[i] + P[j] == S:
ans += 1
ヒント3 (ループで書き直す)
変数i, jを導入してi番目のリンゴ、j番目のパイナップルを買う場合の判定文は次のようになります。 i, jはそれぞれ別々に0からN - 1までの値を取ります。クリックでヒントを見る
if A[i] + P[j] == S:
ans += 1
このように変数(ここではi, j)の組み合わせを列挙したい場合は、多重ループを用います。
今回の場合は変数がi, jの2つなので、次のような二重ループになります。for i in range(N):
for j in range(N):
# i, jを使う
テスト入出力
書いたプログラムがACにならず、原因がどうしてもわからないときだけ見てください。
クリックでテスト入出力を見る
テスト入力1
10 520
430 310 230 390 130 220 70 370 400 160
390 120 450 30 250 140 200 300 270 30
テスト出力1
4
テスト入力2
100 500
247 365 116 339 302 274 383 230 444 136 12 100 92 498 16 190 252 438 404 493 46 105 197 254 180 455 233 280 289 286 301 205 200 477 273 481 494 287 202 211 327 359 341 4 51 336 101 416 142 35 401 457 61 179 33 362 180 176 60 247 499 44 279 372 424 439 224 99 379 322 110 159 383 143 164 435 6 461 61 391 38 225 163 273 222 156 33 300 42 228 164 22 244 66 28 208 243 36 417 93
62 231 388 7 434 37 474 357 262 182 36 298 426 331 252 129 113 156 111 441 375 210 492 499 302 287 493 170 262 233 475 406 72 401 468 156 220 1 410 12 223 150 240 159 345 239 455 404 419 19 46 174 499 218 423 145 196 307 401 260 470 424 171 284 437 44 500 316 156 192 422 391 365 485 145 22 410 124 110 182 28 143 203 289 95 85 415 401 191 268 262 341 391 130 364 274 379 443 53 389
テスト出力2
15
テスト入力3
100 1000
500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500
500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500
テスト出力3
10000
解答例
必ず自分で問題に挑戦してみてから見てください。
解答例1 解答例2 (Listに対するfor文を用いた解法)クリックで解答例を見る
# 入力値を読み込む
N, S = map(int, input().split())
A = list(map(int, input().split()))
P = list(map(int, input().split()))
# りんごとパイナップルを1つずつ購入する組み合わせが、合計S円になる方法の数を計算する
ans = 0 # 方法の数をカウントするための変数
# すべての組み合わせをチェック
for i in range(N):
for j in range(N):
if A[i] + P[j] == S:
ans += 1
# 結果を出力
print(ans)
# 入力値を読み込む
N, S = map(int, input().split())
A = list(map(int, input().split()))
P = list(map(int, input().split()))
# りんごとパイナップルを1つずつ購入する組み合わせが、合計S円になる方法の数を計算する
ans = 0 # 方法の数をカウントするための変数
# すべての組み合わせをチェック
for x in A:
for y in P:
if x + y == S:
ans += 1
# 結果を出力
print(ans)
Time Limit: 2 sec / Memory Limit: 1024 MiB
問題文
明日、英語の単語テストがあります。出題される可能性のある単語は S_1, S_2, \dots, S_N の N 個です。
長い単語が好きであるあなたは、これらの単語のうち K 文字以上の単語を勉強することにしました。
S_1, S_2, \dots, S_N から K 文字以上 の単語を取り出し、順序を変えず空白区切りで 1 行に出力してください。
注意: この問題は for 文のみで正答することが可能です。しかし本節は内包表記の節ですので、極力内包表記を使った回答を考えてみてください。
制約
- 1 \le N \le 20
- 1 \le K \le 16
- S_i\ (1 \le i \le N) は英小文字からなる長さ 1 以上 15 以下の文字列である。
入力
入力は以下の形式で標準入力から与えられる。
N K S_1 S_2 \cdots S_N
出力
S_1, S_2, \dots, S_N から K 文字以上 の単語を取り出し、順序を変えず空白区切りで 1 行に出力せよ。
ただし、K 文字以上の単語が 1 個もない場合は、空行を出力せよ。
入力例 1
8 6 apple is solution dog hospital tree success cat
出力例 1
solution hospital success
apple
, is
, solution
, dog
, hospital
, tree
, success
, cat
のうち、6 文字以上の単語は solution
, hospital
, success
の 3 個です。
入力例 2
10 5 study pen computer car table bus education phone veil aide
出力例 2
study computer table education phone
入力例 3
5 10 selection island advice training trial
出力例 3
テスト入出力
書いたプログラムがACにならず、原因がどうしてもわからないときだけ見てください。
クリックでテスト入出力を見る
テスト入力1
1 1
a
テスト出力1
a
テスト入力2
20 16
appearance relationship instruction proof recommendation analysis development accommodation requirement transportation announcement assistance difference understanding identification freedom population aspect conference environment
テスト出力2
テスト入力3
20 13
responsibility advertisement achievement determination embarrassment opportunity explanation imagination information independence consideration application introduction organization presentation communication appreciation cooperation observation preparation
テスト出力3
responsibility advertisement determination embarrassment consideration communication
解答例
必ず自分で問題に挑戦してみてから見てください。
クリックで解答例を見る
# 入力
N, K = map(int, input().split())
S = input().split()
# K文字以上の単語を抽出
result = [word for word in S if len(word) >= K]
# 空白区切りで出力
print(*result)
Time Limit: 2 sec / Memory Limit: 1024 MiB
問題文
あるゲーム大会に 1 から N の番号が割り当てられた N 人が参加し、総当たり形式で試合が行われます。これまでに M 回の試合が行われました。i 番目 (1 ≤ i ≤ M) の試合では参加者 A_i と参加者 B_i が勝負し、勝者は参加者 A_i で、敗者は参加者 B_i でした。
M 回の試合が終了した時点での試合結果の表を作成し、出力してください。
制約
- 1\le N\le 100
- 0\le M\le \frac{N\cdot(N-1)}{2}
- 1\le A_i, B_i\le N\ (1 \le i \le M)
- A_i \neq B_i\ (1 \le i \le M)
- 同じ参加者同士が 2 回以上試合を行うことはない
- 入力される値はすべて整数である
入力
入力は次の形式で標準入力から与えられる。
N M A_1 B_1 A_2 B_2 A_3 B_3 : : A_M B_M
出力
M 試合が終了した時点での試合結果の表を以下の形式で出力せよ。
R_{1, 1} R_{1, 2} R_{1, 3} \cdots R_{1, N} R_{2, 1} R_{2, 2} R_{2, 3} \cdots R_{2, N} R_{3, 1} R_{3, 2} R_{3, 3} \cdots R_{3, N} \vdots \vdots \vdots \vdots \ddots \vdots R_{N, 1} R_{N, 2} R_{N, 3} \cdots R_{N, N}
ここで、R_{i, j} (1 ≤ i,j ≤ N) は以下で定義される文字である。
- 参加者 i と参加者 j が試合をした場合
- その試合で参加者 i が勝ったなら、R_{i, j} = {}
o
- その試合で参加者 i が負けたなら、R_{i, j} = {}
x
- 参加者 i と参加者 j が試合をしていない場合
- R_{i, j} = {}
-
入出力例も参考にしてください。
サンプルプログラム
N, M = map(int, input().split()) AB = [list(map(int, input().split())) for i in range(M)] # ここにコードを追記する
ジャッジでは以下の入力例以外のケースに関してもテストされることに注意。
入力例1
3 2 1 2 3 1
出力例1
- o x x - - o - -
- 最初の試合では参加者 1 と参加者 2 が試合を行い参加者 1 が勝ったので、R_{1, 2} = {}
o
、R_{2, 1} = {}x
となります。 - 2 番目の試合では参加者 3 と参加者 1 が試合を行い参加者 3 が勝ったので、R_{3, 1} = {}
o
、R_{1, 3} = {}x
となります。 - これ以外の試合は行われていないので、残りのマスは R_{i, j} = {}
-
となります。
入力例2
7 12 1 5 5 4 6 2 1 7 2 4 6 3 1 3 6 4 3 7 5 7 4 3 6 1
出力例2
- - o - o x o - - - o - x - x - - x - x o - x o - x x - x - - o - - o o o o o - - - x - x - x - -
入力例3
1 0
出力例3
-
ヒント
まずは、試合結果の表をプログラムで管理するために、N\cross Nの2次元配列を用意しましょう。 初期値が0で縦a行、横b列の2次元配列の宣言方法は次の通りでした。クリックでヒントを開く
result = [[0] * b for _ in range(a)]
テスト入出力
書いたプログラムがACにならず、原因がどうしてもわからないときだけ見てください。
データが大きすぎるため省略クリックでテスト入出力を見る
テスト入力1
4 6
1 2
1 3
1 4
2 3
2 4
3 4
テスト出力1
- o o o
x - o o
x x - o
x x x -
テスト入力2
20 56
14 18
8 19
18 20
5 15
17 3
12 15
7 3
14 12
18 17
2 12
4 12
17 5
11 10
14 13
8 5
8 1
16 13
17 7
16 18
20 8
10 7
9 20
17 11
8 2
6 4
9 19
13 3
7 15
13 9
4 2
18 7
20 2
17 2
8 18
5 16
1 12
6 1
11 2
9 6
11 15
17 19
18 6
8 17
15 10
10 6
1 18
15 19
9 7
2 14
4 19
20 11
16 12
5 2
16 9
13 2
12 20
テスト出力2
- - - - - x - x - - - o - - - - - o - -
- - - x x - - x - - x o x o - - x - - x
- - - - - - x - - - - - x - - - x - - -
- o - - - x - - - - - o - - - - - - o -
- o - - - - - x - - - - - - o o x - - -
o - - o - - - - x x - - - - - - - x - -
- - o - - - - - x x - - - - o - x x - -
o o - - o - - - - - - - - - - - o o o x
- - - - - o o - - - - - x - - x - - o o
- - - - - o o - - - x - - - x - - - - -
- o - - - - - - - o - - - - o - x - - x
x x - x - - - - - - - - - x o x - - - o
- o o - - - - - o - - - - x - x - - - -
- x - - - - - - - - - o o - - - - o - -
- - - - x - x - - o x x - - - - - - o -
- - - - x - - - o - - o o - - - - o - -
- o o - o - o x - - o - - - - - - x o -
x - - - - o o x - - - - - x - x o - - o
- - - x - - - x x - - - - - x - x - - -
- o - - - - - o x - o x - - - - - x - -
テスト入力3
解答例
必ず自分で問題に挑戦してみてから見てください。
クリックで解答例を見る
N, M = map(int, input().split())
AB = [list(map(int, input().split())) for i in range(M)]
result = [["-"] * N for _ in range(N)]
for a, b in AB:
a -=1
b -=1
result[a][b] = "o"
result[b][a] = "x"
for row in result:
print(" ".join(row))
Time Limit: 2 sec / Memory Limit: 1024 MiB
問題文
あなたは A 社を経営する社長です。A 社は N 個の組織からなり、それぞれに 0 から N - 1 までの番号が付いています。組織 0 はトップの組織です。
組織間には親子関係があり、組織 0 以外の N - 1 個の組織にはそれぞれ 1 つの親組織が存在します。
組織 i\ (1 ≤ i ≤ N - 1) の親組織は組織 p_i です。
ここで、ある組織からその組織の親組織をたどることを繰り返すと、必ずトップの組織(組織 0)に到達します。
あなたは全ての組織に報告書を提出するように求めました。
混雑を避けるために、「各組織は子組織の報告書がそろったら、自身の報告書 1 枚を加えて親組織に送る」ことを繰り返します。子組織が無いような組織は自身の報告書 1 枚だけをすぐに親組織に送ります。
トップの組織は子組織の報告書がそろったら、自身の報告書を加えて社長に提出します。
各組織について、「その組織が親組織に提出することになる報告書の枚数」を出力するプログラムを作成してください。
ただしトップの組織については「社長に提出する報告書の枚数」を出力してください。
制約
- 1 ≤ N ≤ 50
- 0 ≤ p_i ≤ N - 1\ (1 ≤ i ≤ N - 1)
- ある組織からその組織の親組織をたどることを繰り返すと、必ずトップの組織(組織 0)に到達する。
- 入力される値はすべて整数である。
入力
入力は次の形式で標準入力から与えられます。
N p_1 p_2 p_3 \ldots p_{N - 1}
組織 0 はトップの組織であり、親組織が存在しないことに注意してください。
出力
\text{ans}_0 \text{ans}_1 \text{ans}_2 \vdots \text{ans}_{N - 1}
N 行出力してください。i + 1 行目 (0 ≤ i ≤ N-1) には、組織 i についての答え \text{ans}_i を出力してください。
サンプルプログラム
# x 番の組織が親組織に提出する報告書の枚数を返す関数 def num_report(children, x): # ここに追記して再帰関数を実装する # これ以降の行は変更しなくてよい N = int(input()) # p[i] : 組織 i の親組織 # 組織 0 の親組織は存在しないので -1 を入れておく # 組織 0 に相当する部分は入力で与えられないため、リスト [-1] を作成して "+" 演算子で結合する p = [-1] + [int(c) for c in input().split()] # children[i] = 組織 i の子組織のリスト children = [[] for _ in range(N)] for i in range(1, N): parent = p[i] # 組織 i の親組織は parent children[parent].append(i) # parent の子組織リストに i を追加 # 各組織について答えを計算し出力する for i in range(N): res = num_report(children, i) print(res)
関数 num_report
の引数 children
は二次元リストで、組織 x
の子組織の番号一覧はchildren[x]
で得ることができます。
ジャッジでは以下の入力例以外のケースに関してもテストされることに注意。
入力例1
6 0 0 1 1 4
出力例1
6 4 1 1 2 1
組織の関係は次の図のようになります。(子組織から親組織の向きに矢印が向いています。)
番号の隣に書いてある青い数字が各組織についての答えです。
入力例2
8 0 0 1 2 0 3 6
出力例2
8 4 2 3 1 1 2 1
組織の関係は次の図のようになります。(子組織から親組織の向きに矢印が向いています。)
番号の隣に書いてある青い数字が各組織についての答えです。
ヒント
次のようにすることで この問題は 2.05.再帰関数 の例題に非常に近い問題となっているので、そちらも参考にしてください。クリックでヒントを開く
x
番の組織の子組織についての処理が行えます。for c in children[x]:
# (c についての処理)
テスト入出力
書いたプログラムがACにならず、原因がどうしてもわからないときだけ見てください。
クリックでテスト入出力を見る
テスト入力1
20
0 1 1 3 0 0 1 3 5 9 6 8 8 8 0 4 7 12 13
テスト出力1
20
13
1
9
2
3
2
2
6
2
1
1
2
2
1
1
1
1
1
1
テスト入力2
30
0 1 0 1 0 3 1 2 8 6 1 3 0 4 2 3 0 14 16 13 5 2 16 14 19 2 7 2 26
テスト出力2
30
16
8
8
4
2
2
2
2
1
1
1
1
2
3
1
4
1
1
2
1
1
1
1
1
1
2
1
1
1
テスト入力3
50
0 0 0 2 3 1 6 3 4 0 9 5 2 12 7 13 14 1 3 14 2 10 17 7 8 1 15 11 19 29 26 20 7 18 10 24 19 2 19 29 29 9 21 42 0 19 24 44 26
テスト出力3
50
14
13
18
7
7
8
7
2
6
3
2
6
2
5
2
1
2
2
8
2
2
1
1
3
1
3
1
1
4
1
1
1
1
1
1
1
1
1
1
1
1
3
1
2
1
1
1
1
1
解答例
必ず自分で問題に挑戦してみてから見てください。
クリックで解答例を見る
# x 番の組織が親組織に提出する報告書の枚数を返す関数
def num_report(children, x):
# ここに追記して再帰関数を実装する
# ベースケース
if not children[x]:
return 1
# 再帰ステップ
ans = 1 # 自身の報告書
for c in children[x]:
ans += num_report(children, c)
return ans
# これ以降の行は変更しなくてよい
N = int(input())
# p[i] : 組織 i の親組織
# 組織 0 の親組織は存在しないので -1 を入れておく
# 組織 0 に相当する部分は入力で与えられないため、リスト [-1] を作成して "+" 演算子で結合する
p = [-1] + list(map(int, input().split()))
# children[i] = 組織 i の子組織のリスト
children = [[] for _ in range(N)]
for i in range(1, N):
parent = p[i] # 組織 i の親組織は parent
children[parent].append(i) # parent の子組織リストに i を追加
# 各組織について答えを計算し出力する
for i in range(N):
res = num_report(children, i)
print(res)
Time Limit: 2 sec / Memory Limit: 1024 MiB
問題文
次のプログラムでは f0
から f5
の 6 つの関数が定義されており、main
関数から呼び出されています。
それぞれの関数の計算量を見積もって、最も計算時間のかかる関数を呼び出している行をコメントアウトし、PyPy で提出してください。
制限時間は 2 秒です。なお、AtCoder のジャッジでは 1 秒あたり 10^8 〜 10^9 回程度の計算が可能です。
最も計算時間のかかる関数を呼び出している行がコメントアウトされていない場合、提出結果は
また、PyPy ではなく CPython で提出すると、正しくコメントアウトされている場合でも となるので注意してください。
制約を読み、各関数の実行時間を見積もりましょう。
プログラム
def f0(N): return 1 def f1(N, M): s = 0 for i in range(N): s += 1 for i in range(M): s += 1 return s def f2(N): s = 0 for i in range(N): t = N cnt = 0 while t > 0: cnt += 1 t //= 2 s += cnt return s def f3(N): s = 0 for i in range(3): for j in range(3): s += 1 return s def f4(N): s = 0 for i in range(N): for j in range(N): s += i + j return s def f5(N, M): s = 0 for i in range(N): for j in range(M): s += i + j return s # 標準入力から値を取得 N, M = map(int, input().split()) # 計算結果の変数を初期化 a0, a1, a2, a3, a4, a5 = -1, -1, -1, -1, -1, -1 # 計算量が最も大きいものを 1 つだけコメントアウトする (先頭に # をつける) a0 = f0(N) a1 = f1(N, M) a2 = f2(N) a3 = f3(N) a4 = f4(N) a5 = f5(N, M) # 結果を出力 print(f"f0: {a0}") print(f"f1: {a1}") print(f"f2: {a2}") print(f"f3: {a3}") print(f"f4: {a4}") print(f"f5: {a5}")
制約
- 0 \leq N \leq 10^6
- 0 \leq M \leq 10^2
回答例
答え方の例です。
この例では f0
の呼び出しをコメントアウトしていますが、f0
の計算量は O(1) 時間なので、この回答は不正解 となります。
def f0(N): return 1 def f1(N, M): s = 0 for i in range(N): s += 1 for i in range(M): s += 1 return s def f2(N): s = 0 for i in range(N): t = N cnt = 0 while t > 0: cnt += 1 t //= 2 s += cnt return s def f3(N): s = 0 for i in range(3): for j in range(3): s += 1 return s def f4(N): s = 0 for i in range(N): for j in range(N): s += i + j return s def f5(N, M): s = 0 for i in range(N): for j in range(M): s += i + j return s # 標準入力から値を取得 N, M = map(int, input().split()) # 計算結果の変数を初期化 a0, a1, a2, a3, a4, a5 = -1, -1, -1, -1, -1, -1 # 計算量が最も大きいものを 1 つだけコメントアウトする (先頭に # をつける) # a0 = f0(N) a1 = f1(N, M) a2 = f2(N) a3 = f3(N) a4 = f4(N) a5 = f5(N, M) # 結果を出力 print(f"f0: {a0}") print(f"f1: {a1}") print(f"f2: {a2}") print(f"f3: {a3}") print(f"f4: {a4}") print(f"f5: {a5}")
ヒント
それぞれの関数の時間計算量を示します。 N と M の大きさを考えて、最も実行時間が長いものをコメントアウトしましょう。クリックでヒントを開く
時間計算量
f0
O(1)
f1
O(N+M)
f2
O(N \log N)
f3
O(1)
f4
O(N^2)
f5
O(NM)
テスト入出力
書いたプログラムが AC にならず、原因がどうしてもわからないときだけ見てください。
クリックでテスト入出力を見る
テスト入力1
1000000 100
テスト出力1
f0: 1
f1: 1000100
f2: 20000000
f3: 9
f4: -1
f5: 50004900000000
解答例
必ず自分で問題に挑戦してみてから見てください。
クリックで解答例を見る
def f0(N):
return 1
def f1(N, M):
s = 0
for i in range(N):
s += 1
for i in range(M):
s += 1
return s
def f2(N):
s = 0
for i in range(N):
t = N
cnt = 0
while t > 0:
cnt += 1
t //= 2
s += cnt
return s
def f3(N):
s = 0
for i in range(3):
for j in range(3):
s += 1
return s
def f4(N):
s = 0
for i in range(N):
for j in range(N):
s += i + j
return s
def f5(N, M):
s = 0
for i in range(N):
for j in range(M):
s += i + j
return s
# 標準入力から値を取得
N, M = map(int, input().split())
# 計算結果の変数を初期化
a0, a1, a2, a3, a4, a5 = -1, -1, -1, -1, -1, -1
# 計算量が最も大きいものを 1 つだけコメントアウトする (先頭に # をつける)
a0 = f0(N)
a1 = f1(N, M)
a2 = f2(N)
a3 = f3(N)
# a4 = f4(N)
a5 = f5(N, M)
# 結果を出力
print(f"f0: {a0}")
print(f"f1: {a1}")
print(f"f2: {a2}")
print(f"f3: {a3}")
print(f"f4: {a4}")
print(f"f5: {a5}")