Python初心者のためのABC200
A問題
西暦と世紀の関係を観察してみる.西暦 年が 世紀になるとすると,
N | X |
---|---|
1 〜 100 | 1 |
101 〜 200 | 2 |
2901 〜 3000 | 30 |
どうやら
- を100で割る(割った値を とする)
- を整数に切り上げた値を とする
とすれば良さそう.
割り算は /
を使えばできるし,整数への切り上げはググると math.ceil
という関数が出てくる.
これらを使って,
from math import ceil N = int(input()) N0 = N / 100 X = ceil(N0) print(X)
とすればACできる.
しかし,この解き方はオススメしない.
Pythonの数値には,整数型 int
と小数型 float
の2種類がある.
上のコードにおける各数値の型は以下のようになっている.
N = int(input()) # int N0 = N / 100 # float X = ceil(N0) # int
今回の問題の場合は扱う数が比較的小さいので問題ないが,float
の計算は一般には誤差が生じてしまう.
例えば,222222222222222222を111111111111111112で割って整数に切り上げた値は1(1.999... -> 1)だが,
from math import ceil print(ceil(222222222222222222/111111111111111112)) > 2
となってしまう.そのため,数値はなるべく int
のままで扱う方が望ましい.
Pythonでは,整数の割り算は //
と %
で行うことができる.
a, b = 7, 3 print('aをbで割った商:', a//b) print('aをbで割った余り:', a%b) > aをbで割った商: 2 > aをbで割った余り: 1
これを用いれば,「 を100で割った余りを整数に切り下げた値」は N//100
で求めることができる.一方,整数に切り上げた値はというと,
を整数とする. を整数に切り上げた値は で求められる.
ということを利用すれば,
N = int(input()) X = (N+100-1) // 100 print(X)
のようにしてACを得られる.上記のコードでは,数値を終始 int
で扱っているため誤差の心配はない.
B問題
変数の型には,A問題で扱ったような int
や float
の他に,文字列 str
というものが存在する.
それぞれで行うことのできる操作が違うので,型にゆるいPythonといえども明示してやる必要がある.
int_100 = 100 # int str_100 = '100' # str print(int_100 + int_100) > 200 print(str_100 + str_100) > 100100
数値に対する +
は足し算だが,文字列に対する +
は結合をあらわす.
数値と文字列同士を足し算しようとするとエラーになる.
今回の問題のポイントを整理すると,
- 決められた操作を 回行う ->
for
を使う- の値に応じて操作が異なる ->
if
を使う- 数値的な操作と文字列的な操作の2種類がある ->
int
,str
を使い分ける
となるので,以上を丁寧に実装してやると良い.
N,K = map(int,input().split()) # 操作をK回繰り返す for i in range(K): # Nが200で割り切れるかどうかで場合分け if N%200==0: N = N//200 else: N = str(N) #文字列に変換する N = N + '200' N = int(N) #整数に戻す print(N)
なお,else
以下の操作は「を1000倍してから200を足す」という操作に置き換えれば,いちいち文字列に変換する必要はなくなる.
基本的に文字列の結合は遅く文字列の長さに比例した計算時間がかかるため,長い文字列の結合を扱う問題の場合には注意する必要がある.
C問題
まずは素直に実装してみる.
N = int(input()) A = list(map(int,input().split())) ans = 0 for i in range(N): for j in range(i+1,N): if (A[i]-A[j])%200==0: ans += 1 print(ans)
無事TLEが得られる. この方法だと,調べる必要のある の組は 通りあり, という制約から最大で 通り程度になる. AtCoderのジャッジシステムにおいて2秒以内で可能な計算回数は 程度であるため, 上記の解法では制限時間に間に合わないことが分かる. 計算量の話をする際によくオーダーという言葉が用いられるが, 上記の解法は に比例した計算時間がかかる の解法である. 今回の問題の場合, の解法では例えC++などの速い言語を用いたとしてもTLEしてしまうため,別の解法を考える必要がある.
ここで を固定した場合に条件をみたす の個数の効率的な求め方を考えてみる. 条件を満たす というのは,
- をみたす
- と を200で割った余りが等しい
ようなものである. の値自体に興味はなく, の大小関係と を200で割った余りさえ気にすればいいことが分かる. そのため,
]:のうち200で割った余りが であるような要素の個数
のようなリストがあれば,ある に対して条件をみたす の個数は ]で求められる.また, は ] に1を足しただけの と等しいので, が小さい順に同じリストを使い回して値だけを更新していけば良い.
N = int(input()) A = list(map(int,input().split())) ans = 0 cnt = [0]*200 for a in A: k = a % 200 ans += cnt[k] cnt[k] += 1 print(ans)
なお,前処理として の要素全てを200で割った余りに変換しておいたり, 最初に「 の要素のうち200で割った余りが であるような要素の個数」を求め, 各 に対して「200で割った余りが であるような の異なる2つの要素を選ぶ場合の数」を求めて足し合わせても良い.
N = int(input()) A = list(map(int,input().split())) # 各要素を200で割った余りに変換 A = [a%200 for a in A] ans = 0 cnt = [0]*200 for a in A: cnt[a] += 1 for c in cnt: ans += c*(c-1)//2 # c個の要素から異なる2つの要素を選ぶ場合の数 print(ans)
いずれの解法も に比例した計算時間しかかからない の解法であるため, 無事ACを獲得できる.