−目次
AtCoder Beginner Contest 126 A~F問題メモ
今回のABCから全6問になりましたが、難しいのが追加されるというより、従来のBとC、CとDの間が補完されたと感じましたね。
A - Changing a Character
問題
- 長さ N の文字列 S は、'A','B','C' で構成される
- K 番目の文字を小文字にして出力せよ
解法
Pythonなら大文字と小文字の変換は 文字列.upper(), 文字列.lower()
で可能。
でも、実務では意外と大小変換ってすることない気がする。入力が半角英字限定の保証とかないし。自然言語処理分野とかなら入力を揃えるために使うんだろうか。
1 2 3 |
n, k = map ( int , input ().split()) s = input () print (s[:k - 1 ] + s[k - 1 ].lower() + s[k:]) |
B - YYMM or MMYY
問題
- 4つの数字からなる文字列 ABCD が与えられる
- 年は西暦の下2桁で表すとして、以下の1つを出力せよ
- AB 年 CD 月として解釈できるなら
'YYMM
' - CD 年 AB 月として解釈できるなら
'MMYY
' - どちらとも取れるなら
'AMBIGUOUS
' - どちらでも取れないなら
'NA
'
解法
('50' 年とかって、1950年か2050年かという意味でAMBIGUOUSな気がする)
年は00~99のどの値でも良くて、月は01~12に限られるので、それで条件分岐する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
def solve(yy, mm): if 1 < = yy < = 12 : if 1 < = mm < = 12 : return 'AMBIGUOUS' else : return 'MMYY' else : if 1 < = mm < = 12 : return 'YYMM' else : return 'NA' s = input () yy = int (s[: 2 ]) mm = int (s[ 2 :]) print (solve(yy, mm)) |
C - Dice and Coin
問題
- 1~N の目が等確率で出るサイコロと、裏表が等確率で出るコインがある
- 以下のルールでゲームをする
- まずサイコロを1回ふり、出た目の得点を得る
- 得点が K 以上になるまでコインを繰り返し振る。表なら得点が2倍、裏なら0点になる
- 得点が K 以上か、0になったらゲームを終了する
- 終了時点で得点が K 以上である確率を求めよ
- 1≤N≤105
- 1≤K≤105
解法
サイコロで得た得点から K 以上になるまでコインで表を出し続けなければ、K 以上にはなれない。途中で1回でも裏を出すとアウト。
N 通りのそれぞれの目が出たときについて、コインを何回振れば勝てるかシミュレーションする。
余談だが、こういう倍々ゲームは(終了が無く、いつでもドロップアウトできるとすると)サンクトペテルブルクのパラドックス といって、直感に反して期待値が発散する問題として知られる。
1 2 3 4 5 6 7 8 9 |
n, k = list ( map ( int , input ().split())) ans = 0 for i in range ( 1 , n + 1 ): tmp = 1 while i * tmp < k: tmp * = 2 ans + = 1 / tmp print (ans / n) |
D - Even Relation
問題
- N 頂点の木の各頂点を白または黒で塗る
- i 番目の辺は、頂点 ui と頂点 vi を距離 wi で結ぶ
- 同じ色で塗った頂点は、距離が偶数となるように塗り分けたい
- 各頂点の塗り方の一例を示せ
解法
適当な頂点 v0 を白で塗ると固定すると、その頂点から距離が奇数の頂点は、黒で塗るしかない。
距離が偶数の点は、とりあえず v0 だけとの関係を考えた場合はどちらに塗ってもいい。
ここで、木は2頂点間の経路が1通りしかないので、頂点同士の距離は足し算で考えられる。
つまり、3頂点 u,v,w があって、u,w の経路上に v があるとすると d(u,w)=d(u,v)+d(v,w) である。(d は頂点間の距離)
v0 からの距離が偶数の点 v1 を黒で塗ってしまうと、距離が奇数のため黒で塗った点 v2 との距離が、奇数になってしまう。
v0 偶数 v1 奇数 v2 白--------?--------黒 |<-------奇数----->|
なので、距離が偶数の点は白に塗らないといけない。
結局、適当な頂点から探索して、距離が偶数なら白、奇数なら黒で塗るという方針でよい。
唯一、全ての頂点間が偶数の場合は好きに塗っていいが、一例を示せばいいので、上記の方針通り全て白で塗っていい。サンプルケース2はいじわる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import sys n = int ( input ()) links = [ set () for _ in [ 0 ] * n] for line in sys.stdin: u, v, w = map ( int , line.split()) u - = 1 v - = 1 links[u].add((v, w)) links[v].add((u, w)) ans = [ - 1 ] * n q = [( 0 , 0 , - 1 )] while q: v, d, p = q.pop() if d % 2 = = 0 : ans[v] = 0 else : ans[v] = 1 for u, w in links[v]: if u = = p: continue q.append((u, d + w, v)) print ( '\n' .join( map ( str , ans))) |
E - 1 or 2
問題
- N 枚のカードには、数字1か2が書かれている(どちらが書かれているかは分からない)
- ヒントが M 個ある
- i 番目のヒントでは Xi,Yi,Zi が与えられる
- (Xi枚目のカード)+(Yi枚目のカード)+Zi= 偶数である
- ここで、何枚かのカードを指定して、そのカードの数字を知ることができる
- 全てのカードの数字を特定するには、最低何枚のカードを指定すればよいか
- 1≤N,M≤105
解法
ヒントで繋がったカード同士は、その中の1枚の偶奇がわかれば、残りの全てのカードもわかる。
1枚目+2枚目=奇数 2枚目+3枚目=偶数 4枚目+5枚目=偶数 ○--○--○ ○--○ ↓ 1--?--? ?--? 1枚目を指定して直接知る ↓ 1--2--2 ?--? 2枚目、3枚目が連鎖的にわかる
ヒントで繋がっていないカードは、分かりようがないので、新しく指定する必要がある。
よって、Union-Find木などで繋がったカード同士を管理し、最終的に繋がっていない木がいくつあるかを数えればよい。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
import sys class UnionFind: def __init__( self , n): self .table = [ - 1 ] * n def _root( self , x): if self .table[x] < 0 : return x else : self .table[x] = self ._root( self .table[x]) return self .table[x] def find( self , x, y): return self ._root(x) = = self ._root(y) def union( self , x, y): r1 = self ._root(x) r2 = self ._root(y) if r1 = = r2: return d1 = self .table[r1] d2 = self .table[r2] if d1 < = d2: self .table[r2] = r1 if d1 = = d2: self .table[r1] - = 1 else : self .table[r1] = r2 n, m = map ( int , input ().split()) uft = UnionFind(n) for line in sys.stdin: x, y, z = map ( int , line.split()) x - = 1 y - = 1 uft.union(x, y) ans = 0 for v in uft.table: if v < 0 : ans + = 1 print (ans) |
F - XOR Matching
問題
- 以下の条件を満たす、長さ 2M+1 の数列 a={a1,a2,...,a2M+1} を、存在するならば1つ構築せよ
- 0 以上 2M 未満の整数を、それぞれちょうど2つずつ含む
- 同じ数字同士を ai,aj とすると、その間(両端含む)の全ての排他的論理和 ai⊕ai+1⊕...⊕aj=K である
- 全ての同じ数字同士の組に、上記が成り立つ
- 存在しなければ-1を出力せよ
- 0≤M≤17
解法
こういう構築問題で、サンプルケースにまともな成功例が載せられていない場合、見たらすぐにわかるような単純な法則で構築できる「かも」と頭の片隅にとどめる。
まず、K≥2M の場合は、K の最上位ビットを立てるための整数が a に無いので、構築不可能。
M=3 K=9 aの構成要素 000 ~ 111 K 1001 ^ 4ビット目を1にするには、aに少なくとも1つは 4ビット目が1の要素がないといけないが、存在しない
以下、K<2M とする。すると、a には必ず K と等しい要素が存在する。
ここで、xorなので同じ要素2つで打ち消し合えることを活かすと、以下のような方針が思いつく(要出典)。
- K に等しい要素 ak を1つ、中央に置く
- それ以外の要素を、外側に2個ずつくっつけて、入れ子状に覆っていく
M=3 K=5 ↓ 0 1 2 3 4 6 7 5 7 6 4 3 2 1 0 (残り "5" 1個)
ak 以外のペアについては、同じ要素同士が2個ずつ打ち消し合うので中央のakのみが残り、xorの結果は ak=K となる。
問題は ak のペアについてもちゃんと K になるかと言うことである。 残っている片割れの ak は既存の数字の間に入れると成立が崩れてしまうので、末尾か先頭(どっちでも同じ)にくっつけてみると、 ペアの間にあるのは a の構成要素が1個ずつと、ak だけもう1個となる。
0 1 2 3 4 6 7 5 7 6 4 3 2 1 0 5 ~~~~~~~~~~~~~~~~~~~~~~~~~
ここで、0以上 2M 未満の全ての整数のxorを考えると、M≥2 の場合は全て0になる。(4n~4n+3 のxorは0になるので)
この性質を使えば、M≥2 については以下が言える。
ak 同士の間にある数のxor =「0以上 2M 未満のxor」xor ak=0 xor ak=ak=K
よって、ak のペアもちゃんと K にできることが言えた。
M=1 の場合のみ個別に対処する必要があるが、K=0 なら 0 0 1 1
、それ以上なら不可能と、簡単な場合分けで記述できる。
この例外をきちんとサンプルケースに入れてくれているあたり、優しさを感じる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
m, k = map ( int , input ().split()) if m = = 1 : if k = = 0 : print ( * [ 0 , 0 , 1 , 1 ]) else : print ( - 1 ) exit() n = 2 * * m if k > = n: print ( - 1 ) exit() ans = [ 0 ] * (n * 2 ) i = 1 for v in range (n): if v = = k: ans[ 0 ] = ans[n] = v continue ans[i] = ans[ 2 * n - i] = v i + = 1 print ( * ans) |
今回、ジャッジが特定の時刻から詰まってしまったようですが、F問題は見落としがちな例外処理がいくつかあるので、 ジャッジでWAとすぐに判定されて見直したか、終了までわからなかったかでAC率に結構な差が生じたっぽいですね。
別解法
条件を満たす小さなブロックを作って並べる解法。
K<2M かつ M≥2 の時、0以上 2M 未満の数字から、重複無く以下のようなペアが 2M/2 個取れる。
- (a,b):a xor b=K
このペアを2つ使って以下のように組み合わせると、それぞれの間のxorが K になっている。
a xor b = c xor d = K a b c d b a d c
これを並べれば、答えとなる。