ABC203 C問題 Friends and Travel costs を工夫してシンプルに解く
ABC203 C問題を解説します。基本的には公式解説と同じ方針です。
問題の概要
個の村があり、1円払うと1つ先の村へ行けます。
友達がいる村 では円もらえます。
1つの村に複数友達がいる場合もあります。
何番目の村までたどり着けるか求める問題です。
難しいポイント
村の数がと多いのと、友達がいる村も最大でで多いです。
例えば、友達のいる場所と金額を連想配列に保存して、到達した村まででもらえるお金を合計するなんてことをやろうとすると、
すぐにの地獄に引きずり込まれます。
解き方の方針
私は遠回りしながら解法に辿り着きましたが、現実的な制約が友達の数に着目すると割と思いつくかもしれません。
村は1円ずつ消費しながら1個ずつ進むので、友達のいる村に到達できないことはあっても飛び越すことはないです。
5番目の村で6円持ってたら、必ず番目の村に到達できます。
まず友達の位置と友達からもらえる金額を配列に保存して、位置でソートします。
到達できる位置を変数にしておきます。
友達の配列をループして、到達できる位置と友達の位置を比較して、到達できる位置の範囲に友達がいれば、金額の分だけ到達できる位置を足します。
最後に到達できる位置を出力します。
実装
N, K = map(int, input().split()) b_arr = [list(map(int, input().split())) for _ in range(N)] b_arr = sorted(b_arr) until = K for b in b_arr: if until >= b[0]: until += b[1] print(until)
遠回りして時間をロスしてしまいました。。。
もっと問題解いてると割とすぐに気づくんだろうなと思いました。
ABC202 D問題 aab aba baa をメモして解く
ABC202 D問題を解説します。
基本は公式解説と同じですが、具体例を書きながら解説します。
問題
A 個の a と B 個の b からなる長さ の文字列のうち、辞書順で K 番目のものを求めてください。
難しいポイント
AもBも最大で30なので、全量は列挙しきれません。
なので辞書順でKという情報から文字列を求めないといけませんが、どうやって。。。
何かしら閃くために具体的に書き出すとが見えては来ますが。。。
解き方の方針
まずは"a"か"b"を頭からつけて導くことを考えます。
具体的にA = 2、B = 3のケースを列挙してみます。
1. aabbb
2. ababb
3. abbab
4. abbba
5. baabb
6. babab
7. babba
8. bbaab
9. bbaba
10. bbbaa
書き出してみると、先頭にaがくるケースは、その後に続く文字のバターン数になっていることがわかります。
この場合では、残り4文字でのパターンの数です。つまり4つの中から1つのaが入る組み合わせの数です。
先頭にbがくるケースは、残り4文字でのパターン数。です。
なので先頭にaが来るのは、Kが以下の時になります。そうでない場合はbです。
1文字目が決まったので、2文字目の場合も同様に処理していきたいのですが、注意が必要です。
Kを具体的に示しながら、解説します。
K = 4だとしたら、1文字目は"a"です。2文字目を考える時に、さっきのように残り3文字でパターン数を考えます。
ここで注意が必要なのは、2文字目が"a"の時にそれ以降は"bbb"になるパターンです。
2文字目に"a"が来る場合は1パターン、"b"が来る場合はで3パターンです。
単純に数式では表現できないということがわかったので、2次元配列にメモします。
aの残りの数とbの残りの数が与えられた時のパターン数memo[a][b]を求めます。
aかbのどちらかが0の時は"aa"や"bbb"などの1パターンしかないので、1になります。
他の場合は、などの組み合わせの数になりますが、パスカルの三角形(二項定理)の要領で左隣と上の数字を足して求めます。
ja.wikipedia.org
もう1つ罠があるので先頭に"b"が来るパターンも考えます。
K=8のパターンをみていきます。
最初に"b"がくる場合は、2文字目を考えるときにKの値に注意が必要です。
K=8のまま考えると、a=2、b=2の時に8番目の数を選ぶということになってしまいます。
ここでは先程の先頭が"a"かどうかの閾値 4を考慮にいれて、という数にKを更新してあげます。
実装
たかだか30文字なので再帰処理で実装しました。
A, B, K = map(int, input().split()) # memo[a][b] aがa個, bがb個の時の場合の数 memo = [[0 for _ in range(31)] for _ in range(31)] memo[0][0] = 1 for i in range(A + 1): for j in range(B + 1): if i == 0: memo[i][j] = 1 continue if j == 0: memo[i][j] = 1 continue memo[i][j] += memo[i][j - 1] + memo[i - 1][j] # 残りa文字、残りb文字の時にk番目の文字列 def get_S(a, b, k): if a == 0: return "b" * b if b == 0: return "a" * a if memo[a - 1][b] >= k: return "a" + get_S(a - 1, b, k) if memo[a - 1][b] < k: return "b" + get_S(a, b - 1, k - memo[a - 1][b]) print(get_S(A, B, K))
ABC201のC問題 Secret Numberを解く
ABC201のC問題を解説します。
問題
高橋くんは、暗証番号を忘れてしまいました。暗証番号は 0 から 9 までの数字のみからなる 4 桁の文字列で、0 から始まる場合もあります。
0 から 9 までの各数字について、高橋くんは以下のように記憶しています。彼の記憶は長さ 10 の文字列 によって表されます。が o のとき : 数字 i は暗証番号に確実に含まれていた。
が x のとき : 数字 i は暗証番号に確実に含まれていなかった。
が ? のとき : 数字 i が暗証番号に含まれているか分からない。
高橋くんが忘れてしまった暗証番号としてあり得るものは何通りありますか?
難しいポイント
3桁以下の数字は頭に0がつくのは、どう処理しようか悩みました。
文字列で数字が含まれているか判定しようか、各桁の数字を抜き出して数字として判定しようか、地味に迷いました。
解き方の方針
4桁なので、0から9999まで全量を探索して、条件に合ってるものをカウントします。
実装
数字は文字列に変換して、oがついた数字が全て含まれているか、xがついた数字が含まれていないことを確認します。
3桁以下の数字については頭に0がつくことが確定なので、0がxの場合、暗証番号の可能性は無くなります。
0がoの時はスルーして次の文字を検索にまわします。
判定する関数は以下のようになりました。
def is_num_ok(num: str) -> bool: for o in ok_num: if o == '0' and len(num) <= 3: continue if not(o in num): return False for n in ng_num: if n == '0' and len(num) <= 3: return False if n in num: return False return True
最終的なコードは以下のようになりました。
S = input() ok_num = [] ng_num = [] count = 0 for i, s in enumerate(S): if s == 'o': ok_num.append(str(i)) if s == 'x': ng_num.append(str(i)) def is_num_ok(num: str) -> bool: for o in ok_num: if o == '0' and len(num) <= 3: continue if not(o in num): return False for n in ng_num: if n == '0' and len(num) <= 3: return False if n in num: return False return True for j in range(10000): str_j = str(j) if is_num_ok(str_j): count += 1 print(count)
意外と方針はすぐに思いつきましたが、もっと早く解けるようになりたいと思いました。
ABC200のC問題 Ringo's Favorite Numbers 2 を工夫して解く
ABC200のC問題を解説します。
問題
200 という整数が大好きなりんごさんのために、次の問題を解いてください。
N 個の正整数からなる数列 A が与えられるので、以下の条件をすべて満たす整数の組 (i, j) の個数を求めてください。
は 200 の倍数である。
難しいポイント
Nの個数が最大でなので、単純な全探索ではタイムオーバーします。
200の倍数というところから計算量を減らす方法に気づけるかどうかがポイントかと思います。
解き方の方針
ポイントになるのは は 200 の倍数という条件なので、余りだけに着目すれば良いことになります。
余りだと数字の範囲は0から199の範囲になります。
数列Aを全部200で割った余りに変換します。
引き算して200の倍数になるのはどういう余りの組み合わせになるか考えます。
例えば、余り1の数字があったときに、そこから0から199の間でどういう余りの数字を引けば200の倍数になるでしょうか。
そういうことを考えると余り1の数字から余り1の数字を引くしかないことがわかります。他の余りの数字を引いても200の倍数にはなりません。
なのでそれぞれの余りについて、同じ余りの数字から2つ選ぶ組み合わせの数を足した数が答えになります。
実装
実装は特別難しいことはしていません。
N = int(input()) a_arr = list(map(int, input().split())) amari = [0 for i in range(200)] for a in a_arr: amari[a % 200] += 1 ans = 0 for i in range(200): ans += (amari[i] * (amari[i] - 1)) // 2 print(ans)
ABC200のD問題 Happy Birthday! 2 を工夫して解く
残念ながらABC200のD問題を解けなかったので、解説しつつふりかえります。
問題の概要
N 個の正整数からなる数列 が与えられます。 以下の条件を全て満たす 2 つの数列
が存在するか判定し、存在する場合はひとつ出力してください。
(省略)
を200で割ったあまりと、を200で割ったあまりが等しい
の中から200で割った余りが等しくなる足し算の組み合わせが2組以上あるかどうか判定して、あればそれを出力、なければ'No'と出力する問題です。
難しいポイント
Nが最大で200なのですが、足し算の組み合わせを単純に考えるととなり、とてつもない数になります。
何かに気づいて間引かないといけないです。
解き方の方針
公式解説の「鳩の巣原理」は気づかなかったです。。。
余りのパターンとしては200で割ったあまりなので、0から199の200通りしかありません。
なので少なくとも201個合計のパターンあれば、2組のパターンが存在してそれが答えになります。
それを考えると、ビット全探索でまでやる必要はなく、数列の中に同じ数字はないのでまで全探索すれば必ず答えはあるはずです。
なければ'No'と出力します。
実装
2次元配列で、同じ余りになる組み合わせを記録しました。
例えば、ans_list[5] = [1, 2, 5] の時は、a_arr[0]+a_arr[1]+a_arr[4]の余りが5になるように記録します。
N = int(input()) a_arr = list(map(int, input().split())) # 2**8の中で答えの組み合わせがある。Nが8より小さければ全通り探索する n = min(N, 8) # 同じ余りとなる組み合わせのリストを保存するリスト # ans_list[5] = [1, 2, 5] はa_arr[0]+a_arr[1]+a_arr[4]の余りが5になる ans_list = [[] for _ in range(200)] for bit in range(2**n): tmp_sum = 0 tmp_list = [] for i in range(n): if bit & (1 << i): tmp_sum += a_arr[i] tmp_list.append(i + 1) amari = tmp_sum % 200 # 同じ余りの組み合わせがあれば答えを出力して終了する if len(ans_list[amari]): print('Yes') print(len(ans_list[amari]), *ans_list[amari]) print(len(tmp_list), *tmp_list) exit() ans_list[amari] = tmp_list print('No')
鳩の巣原理に気づけなくても動的計画法(DP)で解く方法もあったようですが、私には時間内に実装できるほど実力が足りてないです。
もっと勉強して、気づけなくてもDPなどを駆使して解けるようになろうと思います。
ABC141 D問題 Powerful Discount Tickets を優先度付きキューで解く
ABC141D問題を解説します。
優先度付きキュー(heapq)の練習で取り組みました。
問題の概要
個の商品と枚の割引券があります。
1つの商品に複数回割引券を使うことができます。
全部の商品を買うために必要な最小の金額を求める問題です。
難しいポイント
方針はすぐに思いつくかもしれませんが、普通の配列を使って最大値を取り出したり、要素を追加したりすると、の計算量がかかってしまいます。
、は最大でなので、タイムオーバーします。
解き方の方針
一番高い値段に割引券を使うのが一番お得になります。
1枚ずつ1番高い値段の商品に割引券を使っていきます。
最大値を取り出す、要素の削除、要素の追加を高速に処理するために優先度付きキュー(heapq)を使います。
heapqのheappopでで最小値を取り出せますし、で要素を追加できます。
実装
heapq.heappopは最小値を取り出すので、データを読み込むときに-1をかけて、大小を逆転させておきます。
切り捨てのときにマイナスになっていると、値が大きくなってしまいます。
例えば、-13を2で割った切り捨ては-7になってしまいます。
なので、2で割る前に-1をかけて、割ってから-1をかけてあげます。
from heapq import heapify, heappop, heappush N, M = map(int, input().split()) # 全ての値に-1をかけて大小を逆転させる a_arr = list(map(lambda x: int(x) * -1, input().split())) # 配列を優先度付きキューに変換する heapify(a_arr) for i in range(M): most_expensive = heappop(a_arr) # マイナスのまま切り捨ての割り算をすると大きい値になるので、 # 一度-1をかけてから2で割って、最後に-1をかける most_expensive = ((-1 * most_expensive) // 2) * -1 heappush(a_arr, most_expensive) print(-1 * sum(a_arr))
ABC006 B問題 トリボナッチ数列をメモ化再帰で解く
ABC006のB問題を解説します。
問題の要約
3つ前までの数字を足した数列(トリボナッチ数列)の項目を求める問題です。
数字が大きくなるのでで割ったあまりが求められています。
となります。
解き方の方針
4項目からn項目まで順番に答えを配列に記録しておきます。
足すときに数字が大きく、求められていることがで割ったあまりなので、足した後にで割ったあまりを記録しておきます。
足し算の場合、途中で何回も割ったあまりを求めても答えは変わりません。
@drkenさんの記事を紹介しておきます。
実装
n = int(input()) MOD = 10 ** 4 + 7 # 最初の3項は答えが決まっているので、すぐに答えをprintして終了する if n in [1, 2]: print(0) exit() if n == 3: print(1) exit() tribo_memo = [0 for _ in range(n + 1)] tribo_memo[3] = 1 # 4項からn項まで順番に答えを求めて配列に記録する for i in range(4, n + 1): tribo_memo[i] = (tribo_memo[i - 1] + tribo_memo[i - 2] + tribo_memo[i - 3]) % MOD print(tribo_memo[n])