Beginning of Beginners Contest 002 解説
A問題
各桁の数に興味がある場合には,入力を整数ではなく文字列として受け取ると,実装が簡単になることが多いです.
(別解)入力を整数として受け取り, で割った商と余りを利用する.
N = input() ans = 0 for x in N: ans = max(ans, int(x)) print(ans)
B問題
は見慣れない演算かもしれませんが,ゲームの勝敗を判定するGrundy数の計算などに用いられます. を求めるためには, について順に が に含まれるかどうかを判定し, に含まれないものがあれば を出力し計算を終了すれば良いです.
の計算量を考えてみます. が に含まれるかどうかを判定するために, に含まれる要素1つ1つについて と比較する場合,最悪で の計算量がかかります.しかし,setなどのデータ構造を利用することで,計算量を や に改善することができます.
以上をふまえ,今回の問題を考えてみます. を計算する場合には , を計算する場合には となるので, 個の と 個の を求めるために要する計算量は, または になります. の計算量を改善しない場合には,計算量は となりTLEしてしまいます.
H,W = map(int,input().split()) A = [list(map(int,input().split())) for _ in range(H)] def mex(S): for x in range(1001): if x not in S: return x R = [] for i in range(H): S = set() for j in range(W): S.add(A[i][j]) R.append(mex(S)) C = [] for j in range(W): S = set() for i in range(H): S.add(A[i][j]) C.append(mex(S)) print(*R) print(*C)
C問題
排他的論理和(XOR)は,AND,ORなどと同じ論理演算の仲間です.計算は少しややこしいですが,以下のような性質を持っています.
- 逆元は自分自身:
- 演算の順番を変えても結果は変わらない(結合法則および交換法則が成り立つ)
以上の性質を用いることで, が成り立つことが分かります.
上記のような性質を知らない場合でも,コンテスト中に「競プロ 排他的論理和 性質」のように検索することが大事です.
(別解)各ビットごとに注目する. のうち,第 ビット目が であるものの個数を とする. が奇数のとき, の第 ビット目は であり, が偶数のとき, の第 ビット目は である.
N,K = map(int,input().split()) A = list(map(int,input().split())) ans = K for a in A: ans ^= a print(ans)
D問題
とすると, が成り立ちます.また,各 に対して, とすると が成り立ちます.さらに, はそれぞれ 以下という条件を満たします.よって, が取り得る値は の 通りです.
この解法は,
- 各 に対し, となるための条件は何か?
→ がそれぞれ の倍数であれば良い. - がそれぞれ の倍数であるような は存在するか?
→ ,, それぞれの範囲に の倍数が含まれれば良い.
→ 最小の の倍数である が含まれるかどうかのみを考えれば良い.
という考察から発想に至ることができます.
(別解) は の約数に限られるので, について全探索を行い, の各約数 について が成り立つような が存在するか判定する.この判定は により で可能である. より,前もって 以下の整数について約数列挙を行うことで,計算量を削減することができる.
X,Y,Z = map(int,input().split()) print(min(X,Y,Z))
E問題
多倍長整数を扱うことができるPythonと言えども, のような極めて大きな数を扱うことはできません.しかし,このような大きな数が含まれる場合には, の値は を超えることが予想されます.実際, のとき, より, の値は必ず を超えてしまいます.
よって, のときは-1
を出力し, のときは を通常通り計算すれば良いです.言語によってはオーバーフローに気をつけてください.
from math import gcd def lcm(x,y): return x * y // gcd(x,y) A,B = map(int,input().split()) if B>=60: print(-1) exit() L = 1 for x in range(A,B+1): L = lcm(L,2**x-1) if L>10**18: print(-1) exit() print(L)
F問題
考えられる解法をいくつか紹介します.
解法1
から への並び替えを全探索します.考えられる場合の数は 通りであり, の場合だと 通りにもなるため,TLEしてしまいます.
解法2
の順に, のうちのどのペアに割り当てるかを決めていきます.ただし,すでに2つの要素が決まっているペアについては割り当てないようにします.こうすることで, がペアであるときには必ず が成り立ち, と の区別をなくすことで場合の数の削減につながります(これらのペアは, を考えたときの結果が同じであるため,どちらか一方について考えれば十分です).実際,考えられる場合の数は 通りであり, の場合だと 通りになります.解法1と比べて探索数はかなり減少しましたが,それでもTLEしてしまうと思います.
N,K = map(int,input().split()) X = list(map(int,input().split())) Y_list = [[] for _ in range(N)] # i番目のXをj番目のYに割り当てる再帰関数 def rec(i): if i==2*N: if sum([min(Y) for Y in Y_list])==K: print('Yes') exit() return for j in range(N): if len(Y_list[j])<2: Y_list[j].append(X[i]) rec(i+1) Y_list[j].pop() rec(0) print('No')
解法3
解法2について,ペアの順番を入れ替えても結果は変わらないため, は必ず に割り当てられるようにします.こうすることで, が に割り当てられる場合の探索を削減できます.考えられる場合の数は 通りであり, の場合だと 通りになります.この程度の探索数であれば,ACを獲得できると思います.
N,K = map(int,input().split()) X = list(map(int,input().split())) Y_list = [[] for _ in range(N)] Y_list[0].append(X[0]) # i番目のXをj番目のYに割り当てる再帰関数 def rec(i): if i==2*N: if sum([min(Y) for Y in Y_list])==K: print('Yes') exit() return for j in range(N): if len(Y_list[j])<2: Y_list[j].append(X[i]) rec(i+1) Y_list[j].pop() rec(1) print('No')
解法4
解法2について,各 の割り当てを考える際に,まだ空のペアに割り当てる場合には,空のペアの中で番号が最も小さいものに割り当てるようにします.こうすることで,必ず が成り立つため,ペアの順番による無駄な探索を削減することができます.考えられる場合の数は 通りであり, の場合でも 通りとなり,余裕でACを獲得することができます.
N,K = map(int,input().split()) X = list(map(int,input().split())) Y_list = [] # i番目のXをj番目のYに割り当てる再帰関数 def rec(i): if i==2*N: if sum([min(Y) for Y in Y_list])==K: print('Yes') exit() return for j in range(len(Y_list)): if len(Y_list[j])<2: Y_list[j].append(X[i]) rec(i+1) Y_list[j].pop() if len(Y_list)<N: Y_list.append([X[i]]) rec(i+1) Y_list.pop() rec(0) print('No')
解法5
のうち,どの 個をSakkyさんが購入することになるかを考えます.その後,決められた 個をSakkyさんが購入するようなペアの選び方が実際に存在するかを判定します.この判定は,すべての に対し,
- 価格が安い方から 番目までの商品に注目したとき,Sakkyさんが購入する商品の個数が,購入しない商品の個数以上である
が成り立つかどうかで判定できます.考えられる場合の数は 通りであり, の場合でも 通りとなり,余裕でACを獲得することができます.
from itertools import combinations N,K = map(int,input().split()) X = list(map(int,input().split())) X.sort() for v in combinations(range(2*N),N): ok = True cnt = 0 for i in range(2*N): cnt += 1 if i in v else -1 if cnt<0: ok = False break if ok and sum([X[i] for i in v])==K: print('Yes') exit() print('No')
F2問題
F問題におけるAC解の探索数は,それぞれ
- 解法3: 通り
- 解法4: 通り
- 解法5: 通り
でした. の場合は探索数は小さかったですが, の場合だと
- 解法3:約 通り
- 解法4:約 通り
- 解法5:約 通り
となってしまい,どれもTLEしてしまいます.
解法5では,Sakkyさんが購入する商品の組み合わせ 通りを探索しましたが,中には実際にはペアが構成できず実現不可能な組み合わせも含まれていました.実現可能な組み合わせだけを探索することはできないでしょうか.
解法6
解法5で用いたペアの選び方の判定条件を応用することで,ある 個の商品の組み合わせに対し,Sakkyさんがその 個の商品を購入するようなペアの選び方が存在するための必要十分条件は,
- 長さ の文字列 の第 文字目を,価格が安い方から 番目の商品をSakkyさんが購入するならば
(
,購入しないならば)
とするとき, がvalidな括弧列になっている
であることが分かります.こうすることで,長さ のvalidな括弧列を探索する問題に帰着されます.考えられる場合の数は 通り(カタラン数)であり, の場合でも 通りとなり,ACを獲得することができます.
N,K = map(int,input().split()) X = list(map(int,input().split())) X.sort() def rec(i,c,s): if i==2*N: if s==K: print('Yes') exit() return if c<=2*N-i: rec(i+1,c+1,s+X[i]) if c>0: rec(i+1,c-1,s) rec(0,0,0) print('No')