ABC Atcoder プログラミング

【ABC257】PythonでA~D問題を解く!(AtCoder Beginner Contest 257 )

2022年6月25日開催のAtcoder Beginner Contest 257を、備忘的にギリ茶の私の回答と反省を日記的に残しておきます。

今回はC問題でつまづきました。同じ体重の人間が大人と子供で複数人いる場合などがあり、なかなか苦しめられてしまいました。。

A - A to Z String 2

問題文

A を N 個、B を N 個、…、Z を N 個この順に繋げて得られる文字列の先頭から X 番目の文字を求めてください。

oatmilk

N=2 なら「AABBCCDD....ZZ」となりますね。。

制約

  • 1≤N≤100
  • 1≤X≤N×26
  • 入力は全て整数
oatmilk

愚直にやっても十分に間に合いそうです!

回答へのアプローチ

N が最大でも100、最大でも2600文字なので愚直にやりました。A問題ではどれだけ早く提出できるかという点が重要なので、深く考えずの提出です。

N,X = map(int,input().split())

T = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" #出力例からコピペ
 
T_R = []
for t in T:
  for _ in range(N):
    T_R.append(t)
 
print(T_R[X-1])

同様のアプローチでで若干スマートに書くならこんな感じかな

N,X = map(int,input().split())
 
T = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
 
T_R = []
for t in T:
  T_R.append(t*N)
print("".join(T_R)[X-1])

文字列をいちいち生成しないやり方を考えると、「X-1をNで割った値を少数切り捨て」番目のアルファベットを出力すれば良いです。

N,X = map(int,input().split())
T = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
print(T[(X-1)//N])

また、「T = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"」とベタ書きしていますが、ord 関数を使って文字コードを使う方がよりスマートですね。ベタ書きだとそこが間違える可能性がありますので。

例えば「print(ord("A"))」とすると「65」と出力されます。「A」には「65」というコードがつていて、「B」は「66」、「C」は「67」です。また、文字コードを文字に戻すには chr 関数 を用いれば良いです。

N,X = map(int,input().split())
print(chr(ord("A")+(X-1)//N))

B - 1D Pawn

問題文

N 個のマスが左右一列に並んでおり、左から順にマス 1、マス 2、…、マス N と番号づけられています。
また、K 個のコマがあり、最初左から i 番目のコマはマス Ai に置かれています。
これらに対して、Q 回の操作を行います。 i 回目の操作では次の操作を行います。

  • 左から Li 番目のコマが一番右のマスにあるならば何も行わない。
  • そうでない時、左から Li 番目のコマがあるマスの 1 つ右のマスにコマが無いならば、左から Li 番目のコマを 1 つ右のマスに移動させる。 1 つ右のマスにコマがあるならば、何も行わない。

Q 回の操作が終了した後の状態について、Ki=1,2,…,K に対して左から i 番目のコマがあるマスの番号を出力してください。

oatmilk
  • $$N,K,Q$$
  • $$A_1 A_2 \ldots A_K$$
  • $$L_1 L_2 \ldots L_Q$$

が入力で与えられます。

制約

  • $$ 1 \leq K \leq N \leq 200 $$
  • $$ 1\leq A_1 < A_2 < \ldots < A_K \leq N $$
  • $$ 1 \leq Q \leq 1000 $$
  • $$ 1 \leq L_i \leq K $$
  • 入力はすべて整数

回答へのアプローチ

愚直にそのまま書きました。

N,K,Q = map(int,input().split())
A = list(map(int,input().split()))
L = list(map(int,input().split()))
 
M = [0]*N #マスを用意
for a in A: #マス上にコマがあるとこは1
  M[a-1] = 1
for l in L:
  if l == N:
    continue
  else:
    count = 0 #左から何番目かを数える変数
    for i in range(N-1):
      count += M[i] #コマがあるたびcountが増える。(コマがないとこは0なのでカウントされない)
      if count == l and M[i+1]==0: #count がl番目かつ、次のマスのコマがない時だけ処理する。
        M[i+1] = 1
        M[i] = 0
R = []
for i in range(N):
  if M[i] == 1:
    R.append(str(i+1))
print(" ".join(R))

マスをリストで用意してやっていますが、公式解説通りなくてもリストAをそのまま操作すれば解けますね。リストを用意するとその分メモリも食いますし、本当に必要かどうかを考えるべきですね。今回の計算オーダーくらいなら良いですが、大きくなった際は解法の考慮が必要です

N,K,Q = map(int,input().split())
A = list(map(int,input().split()))
L = list(map(int,input().split()))

for i in range(len(L)):
  l = L[i]-1
  if A[l] == N:
    continue
  elif l+1 == K:
    A[l] += 1
  elif (A[l+1] - A[l])>1:
    A[l] += 1
print(*A)

C - Robot Takahashi

問題文

子供と大人があわせて N 人います。i 番目の人の体重は Wi です。
それぞれの人が子供か大人かは、0 と 1 からなる長さ N の文字列 S によって表され、
S の i 文字目が 0 であるとき i 番目の人が子供であることを、1 であるとき i 番目の人が大人であることをさします。

ロボットである高橋君に対して実数 X を設定すると、 高橋君はそれぞれの人に対して、体重が X 未満なら子供、X 以上なら大人と判定します。
実数 X に対してf(X) を、高橋君に X を設定したときに N 人のうち子供か大人かを正しく判定できる人数で定めます。

X が実数全体を動くとき、f(X) の最大値を求めてください。

制約

  • 1≤N≤2×105
  • S は 0 と 1 からなる長さ N の文字列
  • 1≤Wi​≤109
  • N,Wi は整数
oatmilk

体重の制約から、X の全探索は難しそうです。

回答へのアプローチ

X が実数全体を動くとありますが、体重自体は整数という制約があるので、0.5 刻みとかであれば全探索等も可能っちゃ可能ですがもちろん実行時間的には現実的ではなさそうです。

重要なのはどこに区切り・境界を置くかという点だけなので、実際のX の値はさほど大事ではありません。全員の体重を並べてソートして、どこに区切りを置くかを全て試し、f(X)の最大値を出力するやり方を取ります。

そして、区切りを一つ一つ体重を小さい順から調べる場合、逐次 f(X) を計算するのは非効率です。最初に全員大人として、区切りをすり抜けた人が子供か大人かで f(X) を減算・加算すれば良いです。

  1. 二次元リストで体重と属性(大人か子供か)を情報として持ち、体重でソートします。
  2. まずはリストの一番左端に区切りを置き全員大人として、最初の f(X) を設定します。
  3. 体重を小さい方から区切りを置いていきます。
  4. 区切りの左に行った人が子供の場合は f(X) に 1 を加算し、大人の場合は 1 減らします。
  5. 最後に全員子供した場合の f(X) も求めます。

ただ、この問題の若干厄介なとこは同じ体重の人が存在する点です。同じ体重同士の人たちの間に都合よく区切りを置くことはできません。

色々やり方はありそうですが、素朴に同じ体重が続く区間の子供と大人の数を覚えておいて、体重が変わったところで纏めて f(X) に反映するやり方を取りました。

N = int(input())
S = input()
W = list(map(int,input().split()))
L = [] #全員の体重を管理するリストを作ります
for w, s in zip(W,S):
  L.append([w,int(s)]) #[体重,属性(大人か子供化)] の二次元リストにします。
L.sort() #二次元リストのソートは最初の要素(この場合は体重)でソートされます。(同じ体重の場合は、その次の要素でさらにソートされます。この場合は子供→大人の順)
fx = S.count("1") #まずは、リストの一番左端に区切りを置きます。つまり、全員大人だと判定した場合です。
fx_max = fx
adult,child = 0,0 #同じ体重が続く間の子供大人それぞれが何人いたかを覚えておく変数。
for i in range(1,len(L)): #区切りを一つづつ右へずらしていきます
  if L[i][0] == L[i-1][0]: #同じ体重同士の人たちの間に区切りは置けないので、同じ体重が続く間は、子供大人それぞれが何人いたかを覚えておきます。
    if L[i-1][1] == 1:
      adult += 1
    else:
      child += 1
  else:
    if L[i-1][1] == 1:
      fx += (child - adult - 1)
    else:
      fx += (child - adult + 1)
    adult,child = 0,0 #リセットは忘れません。
    if fx > fx_max:
      fx_max = fx
print(max(fx_max,S.count("0"))) #全員子供の場合を試してないので、最後にどちらが大きいか判定します。

D - Jumping Takahashi 2

問題文

高橋君が住んでいる二次元平面上の街には N 個のジャンプ台があります。i 番目のジャンプ台は点 ( xi​, yi​ ) にあり、ジャンプ台のパワーは Pi です。また高橋君のジャンプ力は S で表され、はじめ S=0 です。高橋君が訓練を 1 回行う度に S は 1 増えます。

高橋君は以下の条件を満たす場合に限り、i 番目のジャンプ台から j 番目のジャンプ台にジャンプすることができます。

$$ P_iS \leq |x_i - x_j| + |y_i - y_j| \tag{*}$$

高橋君の目的は、適切に始点とするジャンプ台を決めることで、そのジャンプ台からどのジャンプ台にも何回かのジャンプで移動できるようにすることです。

目的を達成するためには高橋君は最低で何回訓練を行う必要があるでしょうか?

制約

  • 2≤N≤200
  • −109≤xi​,yi​≤109
  • 1≤Pi​≤109
  • $$ (x_i,y_i) \neq (x_j,y_j) \ (i\neq j) $$
  • 入力は全て整数

回答へのアプローチ

本番はこの問題を考えるところまで行けませんでした。

解説などを参考に解いてみます。

O(N4)となるこの解法をPythonで試してみます。

import math
N = int(input())
L = []
for _ in range(N):
  x,y,P = map(int,input().split())
  L.append([x,y,P])

edges = []
for i in range(N):
  for j in range(N):
    if i != j:
      s = math.ceil((abs(L[i][0]-L[j][0])+abs(L[i][1]-L[j][1]))/L[i][2])
      edges.append([s,i,j])
edges.sort()
dp1 = [[0]*N for _ in range(N)]
dp2 = [[0]*N for _ in range(N)]
for i in range(N):
  dp1[i][i] = 1
for c,u,v in edges:
  for s in range(N):
    for g in range(N):
      dp2[s][g] = dp1[s][g] | (dp1[s][u]&dp1[v][g])
  dp1,dp2 = dp2,dp1
  for i in range(N):
    if sum(dp1[i])==N:
      print(c)
      break
  else:
    continue
  break

遅いPythonだとTLEになるみたいです。。(numpyとか使えば上手くいくでしょうか?やってみた感じダメそうでした。。)

以下はワーシャルフロイド法を使った方法です

とりさん

ワーシャルフロイド法はなんだか名前はなんだかイカつくてカッコイイですが、やってることは割と単純で、

各頂点 i から 各頂点 j に行く際、どの頂点 k を経由したら最短かというのを i, j, k の3重ループで求めるものです。

(ちょっと簡単に書きすぎですが)なのでO(N^3)の解法となります。

N = int(input())
L = []
for _ in range(N):
  x,y,P = map(int,input().split())
  L.append([x,y,P])
  
dist = [[0]*N for _ in range(N)]
for i in range(N):
  for j in range(N):
      dist[i][j] = int((abs(L[i][0]-L[j][0])+abs(L[i][1]-L[j][1])+L[i][2]-1)/L[i][2]) #切り上げさせるための処理

for k in range(N):
  for i in range(N):
    for j in range(N):
      dist[i][j] = min(dist[i][j],max(dist[i][k],dist[k][j])) #ワーシャルフロイド法

ans = 10**15
for i in range(N):
  temp = 0
  for j in range(N):
    temp = max(temp,dist[i][j])
  ans = min(temp,ans)
             
print(ans)

ワーシャルフロイドのループの「max(dist[i][k],dist[k][j])」の部分は頂点 k を経由するときに最低限必要なジャンプ力を求めるためです。なのでジャンプ力が大きい方をここでは採用する必要があります。(あくまでも今回の着眼はジャンプ力であり、ジャンプは何回してもコストになることはありません)

-ABC, Atcoder, プログラミング