2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【AtCoder】PythonでABC235 C問題(The Kth Time Query)を解く

Last updated at Posted at 2022-01-16

せっかくQiitaのアカウントを取得しましたので、AtCoderの問題の解説を書いてみたいと思います。

問題

HHKB プログラミングコンテスト 2022(AtCoder Beginner Contest 235)

考察

やりたいこと

まずは落ち着いて問題文を読む。

長さNの数列 A がある。たとえば、A = [ 1 1 2 3 1 2 ] (入力例1の場合) 。
この数列に対して、以下のようなクエリが飛んでくるので、順に答える。

・「1が1番目に出てくる場所はどこか?」 → 「1番目です。」
・「2が2番目に出てくる場所はどこか?」 → 「6番目です。」
・「3が2番目に出てくる場所はどこか?」 → 「ありません。(3は1個しかない)」
・「5が1番目に出てくる場所はどこか?」 → 「ありません。(5は一つもない)」

愚直な探索の計算量

一つのクエリが来たら、Aの先頭から順に要素を見ていって、たとえば「3が2番目に出てくる場所」がどこかを調べれば、Aの要素へのアクセスを $2\times10^5$ 回程度することで、クエリに答えることが出来る。
しかし、クエリは $2\times10^5$ 個あるので、最悪で $(2\times10^5)\times(2\times10^5)=4\times10^{10}$ 回のAの要素へのアクセスが必要となる。

計算の効率化

上記の方法だと、毎回先頭から順に要素を見ている部分が効率が悪く感じられる。
極端な話、仮に最初に「1が3番目に出てくる場所はどこか」というクエリがあったなら、先頭から順に要素を調べて、3つめの「1」が見つかった時点で、「1が1番目に出てくる場所」「1が2番目に出てくる場所」は把握できている。
そもそも、先頭から順に調べる際に、「1」だけに注目するのではなく、他の数字もどこにあったかを覚えておくことも出来る。

image.png

それならいっそのこと、最初に先頭から最後までAを走査(スキャン)して、どの数字が、何番目と何番目にあったかのメモを作ればよい。

image.png

そうすると、クエリに対して(一つ一つAの要素を順に見ずに)即座に答えられる。

・「2が2番目に出てくる場所はどこか?」
 → 2は「3,6」番目にあるので、答えは「6番目」。

・「3が2番目に出てくる場所はどこか?」
 → 3は「4」番目にある。このリストは要素数が1なので、2番目の「3」は「ありません」。

・「5が1番目に出てくる場所はどこか?」
 → メモの中には5は登場していないので、「ありません」。

このようなデータ構造を作ることが出来れば、効率的に解ける。

必要な計算量は、
・メモの作成の際に、Aの要素に合計 $2\times10^5$ 回アクセスして内容を読み取る。
・$2\times10^5$ 個のクエリに対して、即座に答える。
なので、時間制限に十分間に合いそうだ。

実装

上記のようなメモは、「リストの辞書」を使うと実装できる。利用時に
・memo[1] → [1, 2, 5]
・memo[2] → [3,6]
・memo[3] → [4]
・memo[5] → (要素なし)
のように使えるようなイメージだ。

memoを作る手順は以下のようにする。先頭から順番にAの要素を見ていって、
・「1」番目は 1 だった → memo[1] のリストに「1」を追加。
・「2」番目は 1 だった → memo[1] のリストに「2」を追加。
・「3」番目は 2 だった → memo[2] のリストに「3」を追加。
・ (以下同様...)
のようにすればよい。

このような場合は、collections の defaultdict が役に立つ。
詳細は、下記のコードを見ていただければわかるかと思います。

解答

清書した解答 【提出 #28589877】

ABC235C.py
from collections import defaultdict

# 入力を受け取る
n,q = tuple(map(int, input().split()))
a = list(map(int, input().split()))
xk = [ tuple(map(int, input().split())) for _ in range(q)]

# メモの辞書
memo = defaultdict(list)

# メモを作る
for i,ai in enumerate(a):
    memo[ai].append((i+1))  # python は 0-indexed なので、i に 1 を足しておく

# 答えを格納する配列
ans = []
for x,k in xk:
    # A の中に x が存在する個数 = memo[x] の要素数 となる。
    # A の中に x が存在しない場合、 memo[x] = [] (空リスト) になるので、問題ない。
    if len(memo[x])<k:
        ans.append(-1)
    else:
        ans.append(memo[x][k-1])  # python は 0-indexed なので、k から 1 を引く

# 答えを出力
print(*ans, sep='\n')

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?