ARC176 python Atcoderの記録

問題はこちら


A

N, M = list(map(int, input().split()))
print(N*M) # 1の個数はN*M個
"""
考え方
M=1で自由に1を置けるとき:対角線すべてのマスに1を置くのが最もシンプル。
M=1で置く場所を決められたとき:そのマスから対角線と平行なマスに1を置くのが最もシンプル。
M=M'で置く場所を決められたとき:そのマスから対角線と平行なマスに1を置くことをM回行う。マスが重複した場合、自由に選んでいい感じに1を置く。

実装
1を置くマスごとに、斜めのマスを出力する。
1を置いた斜めのラインを管理しておき、重複した数に応じて、まだ1を置いていない斜めのラインに1を置く。
斜めラインの命名は1列目のマスで何行目に1を置くかで行う。(対角線は1、対角線の下のラインだと2, ... N)
"""
diagonal_lines = set()
# 指定されたマスを通る斜めラインの出力
for m in range(M):
    A,B= list(map(int, input().split()))
    diagonal_line = (A-B+1)%N
    if diagonal_line == 0:
        diagonal_line = N
    if diagonal_line in diagonal_lines: # 既に出力済みであれば、次のマスの操作へ。
        continue
    diagonal_lines.add(diagonal_line)
    for i in range(N):
        if (diagonal_line + i)%N == 0:
            temp = N
        else:
            temp = (diagonal_line + i)%N
        print(temp, 1 + i)
# 不足分の斜めラインの出力
counter = M - len(diagonal_lines)
for s in range(1,N+1):
    if counter == 0: # 斜めラインをM個出力していればBreak
        break
    if s in diagonal_lines: # 既に出力済みであれば、次のマスの操作へ。
        continue
    diagonal_lines.add(s)
    counter  -= 1
    for i in range(N):
        if (s + i)%N == 0:
            temp = N
        else:
            temp = (s + i)%N
        print(temp, 1 + i)
    


B
N>Mばかり考えており時間切れ





C





D





E





F





ABC350 python Atcoderの記録

問題はこちら


A

S = input()
T = int(S[-3:])
flag = "No"
if 1<= T <= 315 or 317 <= T <= 349:
    flag = "Yes" 
print(flag) 


B

N,Q = list(map(int, input().split()))
T = list(map(int, input().split()))
teeth = [0] + [1 for _ in range(N)] # 1-indexed
for t in T:
    if teeth[t] == 1:
        teeth[t] = 0
    else:
        teeth[t] = 1
print(sum(teeth))


C

N = int(input())
A = [0] + list(map(int, input().split()))
output = []
K = 0
while A != list(range(N+1)):
    for i in range(1, N):
        if A[i] != i:
            output.append([min(i,A[i]), max(i,A[i])])
            A[A[i]], A[i] = A[i], A[A[i]]
            
            K += 1
print(K)
for l in output:
    print(*l)
# print(A)


D
Union-Findを使えばよいらしい。 要復習

zenn.dev

N, M = list(map(int, input().split()))

class UnionFind:
    def __init__(self, n):
        self.parent = list(range(n))
        self.rank = [0] * n
        self.size = [1] * n  # 各要素が属する部分木の要素数

    def find(self, x):
        if self.parent[x] != x:
            self.parent[x] = self.find(self.parent[x])
        return self.parent[x]

    def union(self, x, y):
        root_x = self.find(x)
        root_y = self.find(y)
        if root_x == root_y:
            return False
        if self.rank[root_x] < self.rank[root_y]:
            self.parent[root_x] = root_y
            self.size[root_y] += self.size[root_x]
        elif self.rank[root_x] > self.rank[root_y]:
            self.parent[root_y] = root_x
            self.size[root_x] += self.size[root_y]
        else:
            self.parent[root_y] = root_x
            self.rank[root_x] += 1
            self.size[root_x] += self.size[root_y]
        return True

    def get_size(self, x):
        root = self.find(x)
        return self.size[root]

uf = UnionFind(N)

for _ in range(M):
    A, B = list(map(int, input().split()))
    A, B = A - 1, B - 1  # 0-indexedに修正
    uf.union(A, B)
    
# 最終的な根を求める
roots = set()
for i in range(N):
    roots.add(uf.find(i))
    
# 根から部分木の要素数を求める
max_edges = 0
for root in roots:
    n = uf.get_size(root)
    max_edges += n * (n - 1) // 2  # 整数の除算
# 現在の辺の数を引いて答えを算出
print(max_edges - M)


E





F





ARC175 python Atcoderの記録

問題はこちら
1完

A
くそ汚いコード。後で書き直したい。

N = int(input())
P = ["-"] + list(map(int, input().split()))
S = "-" + input()
"""
結局みんな右で取るor左で取るを合わせる必要がある。
dp[i]: P[i-1]の人までスプーンをとった時の聞き手の組み合わせ=Sの有り得方
dpの遷移(右側をとることが確定しているとき) 
dp[i] = dp[i-1] *2 if S[P[i]]が両方を選べるとき→スプーンが右側にしかなく、?の時→S[P[i]]が?で、i+1のスプーンしかないとき
dp[i] = dp[i-1]  if P[i]がどちらかを選べるとき→S[P[i]]がLかRで、i+1のスプーンしかない。または→S[P[i]]がRで、両方のスプーンがある。
dp[i] = 0 P[i]が右のスプーンを選べないとき→S[P[i]]がLで、両方のスプーンがあるときor スプーンがない
"""
MOD = 998244353
# 右のスプーンをとるときのdp
dp_R = [1]
spoon = [[] for _ in range(N+1)]
for i in range(N+1):
    spoon[i] = ["L", "R"] if i != 0 else []

for i in range(1,N+1):
    if (S[P[i]] == "L" and spoon[P[i]] == ["L", "R"]) or (spoon[P[i]] ==["L"]) or (spoon[P[i]] ==[]):
        dp_R.append(0)
        break
    elif S[P[i]] == "?" and spoon[P[i]] == ["R"]:
        dp_R.append(dp_R[-1] *2 % MOD)
        spoon[P[i]] = []
        index = P[i] + 1
        if index == N+1:
            result = spoon[1].remove("L")
        else:
            result = spoon[index].remove("L")

    else:
        if spoon[P[i]] == ["L", "R"]:
            dp_R.append(dp_R[-1])
            spoon[P[i]] = ["L"]
            index = P[i] + 1
            if index == N+1:
                result = spoon[1].remove("L")
            else:
                result = spoon[index].remove("L")

        elif spoon[P[i]] == ["R"]:
            dp_R.append(dp_R[-1])
            spoon[P[i]] = []
            index = P[i] + 1
            if index == N+1:
                result = spoon[1].remove("L")
            else:
                result = spoon[index].remove("L")
# 左のスプーンをとるときのdp
dp_L = [1]
spoon = [[] for _ in range(N+1)]
for i in range(N+1):
    spoon[i] = ["L", "R"] if i != 0 else []

for i in range(1,N+1):
    if (S[P[i]] == "R" and spoon[P[i]] == ["L", "R"]) or (spoon[P[i]] ==["R"]) or (spoon[P[i]] ==[]):
        dp_L.append(0)
        break
    elif S[P[i]] == "?" and spoon[P[i]] == ["L"]:
        dp_L.append(dp_L[-1] *2 % MOD)
        spoon[P[i]] = []
        index = P[i] - 1
        if index == 0:
            result = spoon[N].remove("R")
        else:
            result = spoon[index].remove("R")

    else:
        if spoon[P[i]] == ["L", "R"]:
            dp_L.append(dp_L[-1])
            spoon[P[i]] = ["R"]
            index = P[i] - 1
            if index == 0:
                result = spoon[N].remove("R")
            else:
                result = spoon[index].remove("R")
        elif spoon[P[i]] == ["L"]:
            dp_L.append(dp_L[-1])
            spoon[P[i]] = []
            index = P[i] - 1
            if index == 0:
                result = spoon[N].remove("R")
            else:
                result = spoon[index].remove("R")

print((dp_R[-1] + dp_L[-1])%MOD)


B
解説AC
交換したときに累積和が最大で2増加することに気づかなかった。

N, A, B = list(map(int, input().split()))
S = input()
# (なら+1、)なら-1として累積和と途中の最小値を計算。累積和//2が置換の必要数
# またリストのほうが後で都合がよいのでリストに格納
list_S = []
min_count =  float('inf')
fin_count = 0
for s in S:
    if s == "(":
        fin_count += 1
    if s == ")":
        fin_count -= 1
    min_count = min(min_count, fin_count)
    list_S.append(s)
# print(min_count, fin_count)

# 置換のコストを求める
cost = (abs(fin_count)//2)*B
# 置換後のSを考える
if fin_count > 0:
    swap_count = 0
    for i in range(len(list_S)-1,-1,-1):
        if list_S[i] == "(":
            list_S[i] = ")"
            swap_count += 1
            if swap_count >= abs(fin_count)//2:
                break
if fin_count < 0:
    swap_count = 0
    for i in range(len(list_S)):
        if list_S[i] == ")":
            list_S[i] = "("
            swap_count += 1
            if swap_count >= abs(fin_count)//2:
                break
# print(cost)
# もう一度累積和をとって交換の必要回数を求める
min_count = 0
fin_count = 0
for s in list_S:
    if s == "(":
        fin_count += 1
    if s == ")":
        fin_count -= 1
    min_count = min(min_count, fin_count)
# 交換のコストを求める。1交換すると最大で累積和が+2されることに注意
if min_count < 0:
    cost += (abs(min_count)+1)//2 * min(A, 2*B)
print(cost)


C





D





E





F





ABC346 python Atcoderの記録

問題はこちら
4完

A
愚直実装

N = int(input())
# S = input()
A = list(map(int, input().split()))
result = []
for i in range(N-1):
    result.append(A[i]*A[i+1])
print(*result)


B
条件を満たす文字列があると仮定したとき、その文字列が12文字以上であれば "wbwbwwbwbwbw" を含む。
それを取り除いたあとの文字列を考えることで少ない探索回数で答えを求められると考えた。

W, B = list(map(int, input().split()))
Q = (W+B)//12
W -= 7*Q
B -= 5*Q
S = "wbwbwwbwbwbw" + "wbwbwwbwbwbw"
flag = "No"
for i in range(len(S)):
    for j in range(i,len(S)):
        t = S[i:j+1]
        if t.count("w") == W and  t.count("b") == B:
            flag = "Yes"
print(flag)


C
Setを使って重複を取り除く。
Bのほうが難しいと感じた。
PythonのSetやDictでは悪意のあるケースで極端に遅くなる場合があるらしいので、注意する必要があるとのこと。

https://atcoder.jp/contests/abc346/editorial/9635

N, K = list(map(int, input().split()))
A = list(map(int, input().split()))
# N, K = [2*10**5, 2*10**9]
# A = list(range(1,2*10**5+1))
numbers_set = set(A)
# K以上の要素を除去する
numbers_set = {x for x in numbers_set if x <= K}
sum_set = sum(numbers_set)
sum_from_1_to_K = K*(K+1)//2
print(sum_from_1_to_K - sum_set)


D
初めてDPを通すことができた。
下記のように事象を分けて、それぞれ遷移するときのコストの増分などを考えた。
(解説のように事象を分けるほうが汎用的で賢いと思う)
事象0 : dp[i][0]:0から始まり、i文字目まで01の繰り返しになっているときのコスト(悪い文字列)
事象1 : dp[i][1]:1から始まり、i文字目まで10の繰り返しになっているときのコスト(悪い文字列)
事象2 : dp[i][2]:0から始まり、i文字目までで良い文字列となっているときのコスト
事象3 : dp[i][3]:1から始まり、i文字目までで良い文字列となっているときのコスト

N = int(input())
S = "-" + input() # Indexとi文字目をそろえる
C = ["-"] + list(map(int, input().split()))  # Indexとi文字目をそろえる
# print(S,C)
import numpy as np
dp = [[ np.inf for _ in range(4)]]
# print(dp)
# 1文字目の処理
if S[1] == "0":
    dp.append([0, C[1], np.inf, np.inf])
else:
    dp.append([C[1], 0, np.inf, np.inf])
# 関数整備
def func1(n_i, str_i, C_):
    """
    偶数番目なら1、奇数番目なら0にするときのコストを返す関数
    """
    if n_i %2 == 0: 
        if str_i == "0":
            return C_[n_i]
        if str_i == "1":
            return 0
    elif n_i %2 == 1: 
        if str_i == "0":
            return 0
        if str_i == "1":
            return C_[n_i]
    return ValueError # これが返ってくるときはエラー
def func2(n_i, str_i, C_):
    """
    偶数番目なら0、奇数番目なら1にするときのコストを返す関数
    """
    if n_i %2 == 0: 
        if str_i == "0":
            return 0
        if str_i == "1":
            return C_[n_i]
    elif n_i %2 == 1: 
        if str_i == "0":
            return C_[n_i]
        if str_i == "1":
            return 0
    return ValueError # これが返ってくるときはエラー
# 2文字目以降の処理
for i in range(2,N+1):
    ## 各遷移のコストを求める
    # 遷移1:悪01→悪01
    cost1 = func1(i, S[i], C)
    # 遷移2:悪10→悪10
    cost2 = func2(i, S[i], C)
    # 遷移3:悪01→良01
    cost3 = func2(i, S[i], C)
    # 遷移4:悪10→良01
    cost4 = func1(i, S[i], C)
    # 遷移5:良01→良01
    cost5 = func2(i, S[i], C)
    # 遷移6:良01→良01
    cost6 = func1(i, S[i], C)

    ## dpの結果を求める
    temp = [
        dp[-1][0] + cost1,
        dp[-1][1] + cost2,
        min(dp[-1][0] + cost3, dp[-1][2] + cost5),
        min(dp[-1][1] + cost4, dp[-1][3] + cost6)
        ] # appendする用の文字列
    dp.append(temp)
print(min(dp[-1][2], dp[-1][3]))


E
解説AC
後ろから考えることで効率的に考えられる。
また実際のグリッドではなく、未処理の行・列数を管理することで効率的に計算できる。

H, W, M = list(map(int, input().split()))
T, A, X = [[],[],[]]
# input
for _ in range(M):
    t, a, x = list(map(int, input().split()))
    T.append(t)
    A.append(a-1)
    X.append(x)
# print(grid)
# 何色が何マスあるか管理する
dict_result = {0:0}
for x in set(X):
    dict_result[x] = 0
#
done_rows = [False for _ in range(H)]
done_cols = [False for _ in range(W)]
not_done_rows = H -sum(done_rows)
not_done_cols = W -sum(done_cols)

for m in range(M-1, -1, -1):
    t,a,x = T[m], A[m], X[m]
    if t == 1:
        if not done_rows[a]:
            dict_result[x] += not_done_cols
            done_rows[a] = True
            not_done_rows -= 1
    if t == 2:
        if not done_cols[a]:
            dict_result[x] += not_done_rows
            done_cols[a] = True
            not_done_cols -= 1

# 処理されていないマスの数を数える
dict_result[0] += not_done_rows * not_done_cols
# valueが0でないものを取り出す
result = {k: v for k, v in dict_result.items() if v != 0}

print(len(result))
for k,v in sorted(result.items()):
    print(k, v)


F





ARC174 python Atcoderの記録

問題はこちら
2完

A
連続部分列の和の最大/最小を求めればよい。

ark4rk.hatenablog.com

N, C = list(map(int, input().split()))
A =  list(map(int, input().split()))
# print(N,C,A)
def max_subarray(nums):
    max_sum = nums[0]  # 最大の合計を保持する変数を初期化
    current_sum = nums[0]  # 現在の部分列の合計を保持する変数を初期化

    # リストの各要素を順番にチェックして最大部分配列を求める
    for num in nums[1:]:
        # 現在の部分列の合計に次の要素を追加した場合と、次の要素単独の値を比較して大きい方を選択する
        current_sum = max(num, current_sum + num)
        # 現在の部分列の合計が最大合計よりも大きければ更新する
        max_sum = max(max_sum, current_sum)

    return max_sum
def min_subarray(nums):
    min_sum = float('inf')  # 最小の合計を保持する変数を初期化
    current_sum = 0  # 現在の部分列の合計を保持する変数を初期化

    # リストの各要素を順番にチェックして最小部分配列を求める
    for num in nums:
        current_sum += num
        # 現在の部分列の合計が最小合計よりも小さければ更新する
        min_sum = min(min_sum, current_sum)
        # 現在の部分列の合計が0以下になったら初期化する
        if current_sum > 0:
            current_sum = 0

    return min_sum
#
total_sum = sum(A)
if C > 0:
    max_sum = max_subarray(A)
    print(max(total_sum, total_sum - max_sum + max_sum*C))
else:
    min_sum = min_subarray(A)
    print(max(total_sum, total_sum - min_sum + min_sum*C))


B
考え方はあっていたのだが、途中の割り算でfloat型を使っていたせいでWAとなっていた。
無駄なfloat型はやめよう(自戒)

T = int(input())
for _ in range(T):
    A = list(map(int, input().split()))
    P = list(map(int, input().split()))
    total_score = 0
    for i in range(5):
        total_score += A[i]*(i+1)
    need_score = 3*sum(A) - total_score
    # print(total_score, need_score)
    # 必要な金額を求める
    if need_score <= 0:
        print(0)
    else:
        if P[-2] <= P[-1]/2:
            print(int(P[-2]*need_score))
        else:
            if need_score%2 == 0:
                print(int(P[-1]*need_score//2))
            else: # 奇数の時
                print(int(P[-1]*(need_score//2) + min(P[-2], P[-1])))


C





D





E





F





ABC345 python Atcoderの記録

問題はこちら
3完

A

S = input()
T = "<" + "="*(len(S)-2) + ">"
if S == T:
    print("Yes")
else :
    print("No")


B
切上除算を行う問題。知らなかったのでメモ。
a÷b の小数点以下を切り上げた整数を求めることを 切り上げ除算という
答えの求め方は(a+b-1)//b
float型を経由すると誤差により間違う可能性がある

X = int(input())
print((X + 9) // 10)


C
異なる文字を入れ替えるときは必ず違う文字列になる。
よって同じ文字を入れ替えることが何回発生するかをカウントする。

S = input()
def count_lowercase_letters(text):
    # 辞書を初期化して英小文字の出現回数を記録する
    letter_counts = {char: 0 for char in 'abcdefghijklmnopqrstuvwxyz'}

    # 文字列内の各文字を処理し、英小文字の出現回数をカウントする
    for char in text:
        if char.islower():
            letter_counts[char] += 1

    return letter_counts
# 英小文字の出現回数を数える
lowercase_counts = count_lowercase_letters(S)
# print(lowercase_counts)

#
temp = -1
for value in lowercase_counts.values():
    # print(value)
    if value >= 2:
        temp += value*(value-1)*0.5
print(int(len(S)*(len(S)-1)*0.5 - max(temp,0) ))


D





E





F





ABC344 python Atcoderの記録

問題はこちら
4完

A
様々な解法が回答に乗っていて面白い。|で3分割し、1・3番目のみ出力 or |の間を正規表現で置換する方法が早そうな気がした。

S = input()
l = []
for i in range(len(S)):
    if S[i] == "|":
        l.append(i)
print(S[:l[0]] + S[l[1]+1:])


B

l = []
while True:
    A = int(input())
    l.append(A)
    if A == 0:
        break
for i in l[::-1]:
    print(i)


C
有り得る和を先に計算しておけばよい。ただ計算量の概算をミスって無駄に二分探索を書いてしまった。

N = int(input())
A = list(map(int, input().split()))
M = int(input())
B = list(map(int, input().split()))
L = int(input())
C = list(map(int, input().split()))
Q = int(input())
X = list(map(int, input().split()))
# ありうる和を先に計算し、Set型で保持する
set_sum = set()
for a in A:
    for b in B:
        for c  in C:
            set_sum.add(a+b+c)
list_sum = sorted(list(set_sum))
# print(list_sum)
# 二分探索で、xがlist_sum内にあるかを確認する。(この問題では不要らしい)
def serch_from_list(x, list_sum):
    L = 0
    R = len(list_sum)-1
    while R-L > 0:
        M = (L+R)//2
        if list_sum[M] < x:
            L = M+1
        else:
            R = M
    return L
#
for x in X:
    if list_sum[serch_from_list(x, list_sum)] == x:
        print("Yes")
    else:
        print("No")


D
BFS(幅優占探索)かと思って解いていたが、よく考えてみたら計算量が間に合わないため絶対違う。要反省。





E
ある要素の前後が何かを辞書で保持しておく。

N = int(input())
A = ["s"] + list(map(int, input().split())) + ["g"]
Q = int(input())
dict_ascending = {}
dict_descending = {}
for i in range(N+1):
    a,b = [A[i],  A[i+1]]
    dict_ascending[a] = b
    dict_descending[b] = a
# print(dict_ascending)
# print(dict_descending)
#
for _ in range(Q):
    q = list(map(int, input().split()))
    if q[0] == 1:
        x,y = q[1:]
        z = dict_ascending[x]
        dict_ascending[x] = y
        dict_ascending[y] = z
        dict_descending[z] = y
        dict_descending[y] = x
    else:
        x = q[1]
        y = dict_ascending[x]
        w = dict_descending[x]
        dict_ascending[w] = y
        dict_descending[y] = w
#
# print(dict_ascending)
result = ["s"]
while result[-1] != "g":
    result.append(dict_ascending[result[-1]])
print(*result[1:-1])


F