ABC277のA,B,C,D問題をpythonで解説していきます。
なにか、間違っているところなどありましたら、@EitaSugiまで、ご指導ご鞭撻の程、よろしくお願い致します!
A問題
考察
今回、与えられた数列Pにおいて、Xとなる数字のindexを出力すれば良いです。
(※indexは0始まりなので+1するのを忘れずに)
コード
import sys
def LI(): return list(map(int,sys.stdin.readline().rstrip().split()))
def II(): return int(sys.stdin.readline())
def MI(): return map(int,sys.stdin.readline().rstrip().split())
def S(): return sys.stdin.readline().rstrip()
readline = sys.stdin.readline
sys.setrecursionlimit(10 ** 7)
N,X = MI()
P = LI()
for i in range(N):
if P[i] == X:
print(i+1)
exit()
B問題
考察
今回は、Noを出力する場面は、
- 文字列がルールを満たしてない
- 同じ文字列がある
の2つです。
そこで、
1つ目のルールに関しては、文字列を分割して、それぞれ条件にあっているか確認
2つ目の条件については、すべての入力を受け取ったあとに、set関数を使って、長さが変化したら、同じ要素があったと判断
することとします。
コード
import sys
def LI(): return list(map(int,sys.stdin.readline().rstrip().split()))
def II(): return int(sys.stdin.readline())
def MI(): return map(int,sys.stdin.readline().rstrip().split())
def S(): return sys.stdin.readline().rstrip()
readline = sys.stdin.readline
sys.setrecursionlimit(10 ** 6)
N = II()
first_list= ["H","D","C","S"]
second_list = ["A" , "2" , "3" , "4" , "5" ,"6" , "7" , "8" , "9" , "T" , "J" , "Q" , "K"]
box = []
for i in range(N):
s = S()
box.append(s)
for i in range(N):
first = box[i][0]
second = box[i][1]
if first not in first_list:#文字列の1文字目がH,D,C,Sのどれかか
print("No")
exit()
if str(second) not in second_list:#文字列の2文字目がA~Kのどれかか
print("No")
exit()
box.append(s)
if len(set(box)) == N:#入力されたときと長さが同じなら、すべての文字列に重複はない
print("Yes")
else:#文字列の量が変化した場合は、重複があったと判断
print("No")
C問題
考察
1階から行けるところをdfsするなり、
「1階から行ける→1階と連結することができる」と読み替えてUnionFindすると解けます。(僕はdfsが苦手なのでUnionFindで解きました)
しかし、ここで問題になってくるのは、ビルの高さです。
今回、面倒なのは、ビルの高さが10^9階なので、普通に計算しようとすると、TLEしてしまいます。
Question ではどうするか?
ここで注目したいのは、出てくる階の数です。
今回、はしごは最大でも2*10^5本まで出てこないので、はしごで繋がれる階の数は、最大で2*2*10^5=4*10^5です。
そのため出てくる階の数だけ、nodeを作成し、UnionFindしてあげればできます。
コード
import sys
def LI(): return list(map(int,sys.stdin.readline().rstrip().split()))
def II(): return int(sys.stdin.readline())
def MI(): return map(int,sys.stdin.readline().rstrip().split())
def S(): return sys.stdin.readline().rstrip()
readline = sys.stdin.readline
sys.setrecursionlimit(10 ** 6)
import bisect
from typing import List
class UnionFind:
"""0-indexed"""
def __init__(self, n):
self.n = n
self.parent = [-1] * n
self.__group_count = n
def unite(self, x, y) -> bool:
"""xとyをマージ"""
x = self.root(x)
y = self.root(y)
if x == y:
return False
self.__group_count -= 1
if self.parent[x] > self.parent[y]:
x, y = y, x
self.parent[x] += self.parent[y]
self.parent[y] = x
return True
def is_same(self, x, y) -> bool:
"""xとyが同じ連結成分か判定"""
return self.root(x) == self.root(y)
def root(self, x) -> int:
"""xの根を取得"""
if self.parent[x] < 0:
return x
else:
self.parent[x] = self.root(self.parent[x])
return self.parent[x]
def size(self, x) -> int:
"""xが属する連結成分のサイズを取得"""
return -self.parent[self.root(x)]
def all_sizes(self) -> List[int]:
"""全連結成分のサイズのリストを取得 O(N)
"""
sizes = []
for i in range(self.n):
size = self.parent[i]
if size < 0:
sizes.append(-size)
return sizes
def groups(self) -> List[List[int]]:
"""全連結成分の内容のリストを取得 O(N・α(N))"""
groups = dict()
for i in range(self.n):
p = self.root(i)
if not groups.get(p):
groups[p] = []
groups[p].append(i)
return list(groups.values())
@property
def group_count(self) -> int:
"""連結成分の数を取得 O(1)"""
return self.__group_count
N = II()
node_cnt = []
node = []
for i in range(N):
A,B = MI()
node_cnt.append(A)
node_cnt.append(B)
node.append([A,B])
if 1 not in node_cnt:#そもそも1階とどこかの階にはしごがかかっていなかったら1階から動けないので
print(1)
exit()
neo_box = list(set(node_cnt))#出てくる階の数を数えている
neo_box.sort()#bisectを使うので、ソートしておく
uf = UnionFind(len(neo_box))#その階の数だけunionfindのnodeを設ける
for i in range(N):
A = node[i][0]
B = node[i][1]
A_ind = bisect.bisect_left(neo_box,A)#A階は、(ソートされた)出てくる階の中だと何番目のindexを持つのか
B_ind = bisect.bisect_left(neo_box,B)#A階は、(ソートされた)出てくる階の中だと何番目のindexを持つのか
uf.unite(A_ind,B_ind)#AのindとBのindをつなげる
for i in range(len(neo_box)-1,0,-1):#後ろから、つまり大きい数字から探索
if uf.is_same(0,i):#もし、index_0とindex_iが連結されているのなら、
print(neo_box[i])#そのindexを持つ階には到達可能である
exit()
#どの階にも到達できなかった場合
print(1)
exit()
D問題
考察
手札に整数 X または整数 (X+1) mod M(X+1) のカードは捨てることができる
→連続したところであれば、場に数字を出し続けることができる
と言い換えることができます。
今回求めたいのは、手札の総和(合計- 連続したところの数列)を最小にしたいので、
連続した数列を最大化して上げる必要があります。
よって、ソートして差が1以下ならその区間は捨てることができ、2以上ならそこが連続して捨てられる数列の切れ目になります。
手順
- 連続する区間を取りたいので、ソートします。
- もしその数列に切れ目があるのなら、どこの場所でも良いが切れ目から探索します。(M=6で A = [1,2,5,6]ときに、5始まりなら全て消去できるが、1始まりだとすべて消去できないため)
- for文を回していき、
現在地(now)が一つ前の値と同じor連続している場合は、捨てることができ、
そうではないのなら、現在までに捨てることのできる数字の合計を記憶しておく - 数列の合計値-引くことのできる数字を出力すればOK
コード
import sys
def LI(): return list(map(int,sys.stdin.readline().rstrip().split()))
def II(): return int(sys.stdin.readline())
def MI(): return map(int,sys.stdin.readline().rstrip().split())
def S(): return sys.stdin.readline().rstrip()
readline = sys.stdin.readline
sys.setrecursionlimit(10 ** 6)
N,M = MI()
A = LI()
B = []
sum_count = sum(A)#Aの合計値を取得
A.sort()
pre = 0
for i in range(N):
if A[i] - A[i-1] > 1:#連続していない→切れ目の部分なら
pre = i#切れ目として記憶
break
minus = 0#場に捨てることのできる数字の和を記憶
tmp = 0#一時的に記憶させておくための変数
for i in range(N):
now = (pre+i)%N#indexエラーにならないように
if (A[now]-A[(now-1)]) % M > 1:#現在の数字が一つ前の数字と比べ、差が1以上なら
minus = max(minus,tmp)#これ以上捨てれる区間は伸びないので、本区間で捨てた数字とこれまでの区間で捨てた最大値を取る
tmp = 0#一次保存は最初から
tmp += A[now]#現在の数字からまた捨てる区間を開始
minus = max(minus,tmp)#for文が終わったあとに、tmpとminusを比べ大きい方と取る、
print(sum_count - minus)#全体の合計から場に捨てることのできる最大の数値で引く
まとめ
D問題、時間内に解けなかった・・・悔しい。かつっぱさんのyoutubeみてすぐ復習します!!
コメント