見出し画像

【ABC353】緑コーダーの競プロ参加記録 #17

今回はAtCoder Beginner Contest 353。使用言語はPythonです。


A問題 - Buildings

コンテスト中の提出
https://atcoder.jp/contests/abc353/submissions/53322799


  • $${N}$$個のビルがある。

  • $${i}$$番目のビルの高さは$${H_i}$$。

  • $${1}$$番目のビルより高い最初のビルは何番目か出力してね。


$${H_1 < H_i}$$となる最初の$${i}$$を出力すればよく、そのような$${i}$$がなければ答えは -1 です。

リストを全探索するときは 0-index で出力は 1-index になること、2つ以上の出力をしないことに注意が必要です。

""" ACコード """
N = int(input())
H = list(map(int, input().split()))
for i in range(1, N):
    if H[0] < H[i]:
        print(i + 1)
        exit()
print(-1)


B問題 - AtCoder Amusement Park

コンテスト中の提出
https://atcoder.jp/contests/abc353/submissions/53329932


  • $${K}$$人乗りのアトラクションと$${N}$$組のグループがある。

  • $${i}$$番目のグループは$${A_i}$$人。

  • $${i=1}$$から$${i=N}$$まで順番に操作を繰り返す。

  • 操作: グループ単位で可能な限り乗せてスタートする。

  • 何回アトラクションをスタートさせるか出力してね。


問題文が長く読むのが大変ですが、問題文に書かれてあることを丁寧にコードになおしていけばよいです。入力例1の図も参考になります。

$${1 \le N, K \le 100}$$なのでシンプルな実装で 2 sec に間に合います。

""" ACコード """
N, K = map(int, input().split())
A = list(map(int, input().split()))
ans = 0
B = 0
i = 0
while i < N:
    while i < N and B + A[i] <= K:
        B += A[i]
        i += 1
    ans += 1
    B = 0
print(ans)

上記のような while ループではなく、for ループを使って解くこともできます。
その際、最後のグループが取り残されるので答えを +1 することに注意が必要です。

""" 別解 """
N, K = map(int, input().split())
A = list(map(int, input().split()))
ans = 0
B = 0
for i in range(N):
    if B + A[i] <= K:
        B += A[i]
    else:
        ans += 1
        B = A[i]
ans += 1
print(ans)


C問題 - Sigma Problem

コンテスト中の提出
https://atcoder.jp/contests/abc353/submissions/53355538


  • $${f(x, y)}$$は「$${x + y}$$を$${10^8}$$で割った余り」。

  • $${\displaystyle\sum_{i =1}^{N-1}\displaystyle\sum_{j=i+1}^Nf(A_i,A_j)}$$を出力してね。


式に表れているように、素直に解こうとすると$${O(N^2)}$$であり TLE になってしまいます。


和が小さい場合

たとえば、どの$${(A_i, A_j)}$$のペアを選んだとしてもその和が$${10^8}$$未満であるとします。
つまり、$${f(A_i, A_j) = A_i + A_j}$$です。

ある$${i}$$に対しては、$${i < j}$$である$${N - i}$$個の$${(i, j)}$$のペアを作ることができます。
このとき、答えに加算する値は以下のようになります。

$$
\displaystyle\sum_{j =i+1}^N (A_i + A_j) \\
= (N - i) \times A_i +\displaystyle\sum_{j =i+1}^N A_j
$$

$${\displaystyle\sum_{j =i+1}^N A_j}$$は累積和を用いて$${O(1)}$$で計算できます。

説明のため mod 10 としています。

【補足】

$$
\displaystyle\sum_{j =i+1}^N (A_i + A_j)
$$

上式は「$${i + 1 \le j \le N}$$を満たす$${j}$$における$${A_i + A_j}$$の総和」を意味しています。

$${i + 1 \le j \le N}$$を満たす$${j}$$の個数は$${N - i}$$です。
たとえば$${i = 3, N = 8}$$とすると$${j}$$の候補は$${{4, 5,6,7,8}}$$の5個になります。

ここで、上式は次のように分解できます。

$$
\displaystyle\sum_{j =i+1}^N (A_i + A_j) = \displaystyle\sum_{j =i+1}^N A_i + \displaystyle\sum_{j =i+1}^N A_j
$$

また、$${\displaystyle\sum_{j =i+1}^N}$$の中で動く値は$${j}$$であり、$${i}$$は関係ありません。
よって、以下が成り立ちます。

$$
\displaystyle\sum_{j =i+1}^N A_i = (N - i) \times A_i
$$

この手順で、先に述べた式が出てきます。

$$
\displaystyle\sum_{j =i+1}^N (A_i + A_j) \\
= (N - i) \times A_i +\displaystyle\sum_{j =i+1}^N A_j
$$


和が大きい場合

次に、どの$${(A_i, A_j)}$$のペアを選んだとしてもその和が$${10^8}$$以上であるとします。

$${1 \le A_i < 10^8}$$より、$${A_i + A_j}$$の最大値は$${2 \times 10^8 - 2}$$です。
つまり、$${A_i + A_j  (\mathrm{mod}  10^8)}$$と$${A_i + A_j - 10^8}$$が同じ意味になります。

ある$${i}$$に対して、答えに加算する値は以下のようになります。

$$
\displaystyle\sum_{j =i+1}^N (A_i + A_j - 10^8) \\
= (N - i) \times (A_i - 10^8) +\displaystyle\sum_{j =i+1}^N A_j
$$

説明のため mod 10 としています。


問題を解く

ある$${i}$$について、以下のように$${p, q}$$を定めます。

  • $${A_i + A_j < 10^8}$$かつ$${i < j}$$である$${j}$$の個数を$${p}$$

  • $${A_i + A_j \ge 10^8}$$かつ$${i < j}$$である$${j}$$の個数を$${q}$$

このとき$${p + q = N - i}$$であり、答えに加算する値は以下のようになります。

$$
\displaystyle\sum_{i =j+1}^N f(A_i, A_j) \\
= p \times A_i +q \times (A_i - 10^8) +\displaystyle\sum_{j =i+1}^N A_j \\
= (N - i) \times A_i - q \times 10^8 +\displaystyle\sum_{j =i+1}^N A_j \\
$$

ここで、$${f(A_i, A_j) = f(A_j, A_i)}$$なので$${A}$$をソートしても問題ありません。
また、ソート後の$${A}$$に対して$${10^8 - A_i}$$を二分探索をすることで$${q}$$を高速に求めることができます。

説明のため mod 10 としています。

以下のコードでは累積和を itertools.accumulate、二分探索を bisect.bisect_left で行っています。

bisect_left で得られる$${j}$$について常に$${i < j}$$とならないことに注意してください。

from itertools import accumulate
from bisect import bisect_left
mod = 10 ** 8

""" ソートして累積和 """
N = int(input())
A = list(map(int, input().split()))
A.sort()
acc = list(accumulate(A))

""" 二分探索して足したり引いたり """
ans = 0
for i in range(N):
    j = bisect_left(A, mod - A[i])
    q = N - max(i + 1, j)
    ans += acc[N - 1] - acc[i]
    ans += (N - i - 1) * A[i]
    ans -= mod * q
print(ans)

この解法は ABC351- E - Jump Distance Sum や ABC351- F - Double Sum の部分問題に似ています。

公式解説と異なる解法なので、分かりやすいと思う方を参考にしてみてください。


D問題 - Another Sigma Problem

コンテスト中の提出
https://atcoder.jp/contests/abc353/submissions/53369939


  • $${f(x,y)}$$は「$${\mathrm{str}(x) + \mathrm{str}(y)}$$ を整数とみなした値」。

  • $${\displaystyle\sum_{i =1}^{N-1}\displaystyle\sum_{j=i+1}^Nf(A_i,A_j)  }$$を出力してね。$${(\mathrm{mod}  998244353)}$$


C問題と異なり$${f(A_i, A_j) \not= f(A_j,A_i)}$$なので、$${A}$$をソートできません。

$${y}$$の桁数を$${k}$$とすると、$${f(x, y) = x \times 10^k + y}$$です。
$${1 \le A_i \le 10^9}$$より、$${1 \le k \le 10}$$です。

C問題と同様、$${f(x, y)}$$をシンプルな和の形に直せたので、$${x \times 10^k}$$と$${y}$$を独立して計算できるようになりました。

ここで、ある$${i}$$について、$${A_i}$$が$${x}$$になるパターンと$${y}$$になるパターンの2つが考えられます。
それぞれについて、答えに加算する値は以下のようになります。

  • $${A_i}$$が$${x}$$になるパターンでは$${A_i \times 10^k}$$

  • $${A_i}$$が$${y}$$になるパターンでは$${A_i}$$

$${A_i}$$が$${y}$$になるパターンは$${i - 1}$$個あります。

$${A_i}$$が$${x}$$になるパターンは$${N - i}$$個ありますが、$${y}$$の桁数によって加算する値が変わります。
ある$${i}$$について、$${i < j}$$を満たす$${A_j}$$のうち$${k}$$桁のものがいくつあるか分かっている必要がありそうです。

""" cnt[i][k]: A の i 番目以降に k 桁の整数がいくつあるか """
N = int(input())
A = list(map(int, input().split()))
cnt = [[0] * 11 for _ in range(N + 1)]
for i in reversed(range(N)):
    d = len(str(A[i]))
    cnt[i][d] += 1
    for k in range(11):
        cnt[i][k] += cnt[i + 1][k]

$${y}$$の桁数$${k}$$を考えることで、ある$${i}$$を固定したとき$${j}$$の候補の探索を$${O(N)}$$から$${O(10)}$$程度まで減らせました。

""" A_i が x, y になるパターンを分けて加算 """
mod = 998244353
ans = 0
for i in range(N):
    y = A[i]
    ans += y * i
    for k in range(11):
        x = A[i] * pow(10, k)
        ans += x * cnt[i + 1][k]
ans %= mod
print(ans)

今回の制約では$${f(A_i, A_j) \le 10000000001000000000}$$であり、答えは最大でも$${_NC_2 \times 10000000001000000000 \le 10^{30}}$$程度なので、$${\mathrm{mod}}$$の計算は最後に行うだけでもよいです。


一般的には、$${\mathrm{mod}}$$の計算は都度行う方がよいです。
Python の int にオーバーフローはありませんが、大きすぎる数の計算には時間がかかるので TLE の原因になります。

""" mod の計算を都度行う """
mod = 998244353
ans = 0
for i in range(N):
    y = A[i]
    ans += y * i % mod
    ans %= mod
    for k in range(11):
        x = A[i] * pow(10, k) % mod
        ans += x * cnt[i + 1][k] % mod
        ans %= mod
print(ans)


E問題 - Yet Another Sigma Problem

upsolve
https://atcoder.jp/contests/abc353/submissions/53385473


  • $${f(x,y)}$$は「$${x, y}$$ の最長共通接頭辞の長さ」。

  • $${\displaystyle\sum_{i =1}^{N-1}\displaystyle\sum_{j=i+1}^Nf(S_i,S_j)  }$$を出力してね。


公式解説にあるTrie木というものが分からないので独自の解法になります。


$${S}$$の各文字列を1文字目が「'a' のグループ」「'b' のグループ」…「'z' のグループ」に分けてみます。
異なるグループ間の$${f(S_i, S_j)}$$は$${0}$$になります。

1文字目が「'a' のグループ」の中ではどの$${(S_i, S_j)}$$を選んだとしても1文字目が共通接頭辞になるので、「'a' のグループ」のサイズを$${M}$$とすると、答えに加算する値は$${_MC_2}$$です。

次に、1文字目が「'a' のグループ」の文字列すべてから1文字目を削除したものとして、そのグループをさらに1文字目が「'a' のグループ」「'b' のグループ」…「'z' のグループ」に分けてみます。
これを繰り返せばよさそうです。再帰関数が使えます。

$${S}$$をソートすると接頭辞を考えやすくなります。
次のグループのサイズが$${2}$$以上の場合のみ処理を進めるなどの枝刈りをすることで 2 sec に間に合います。

""" ちょっとムリヤリかもしれない解法 """
from collections import deque
from sys import setrecursionlimit
setrecursionlimit(100100100)

""" グループ curr の k 文字目を考える """
def solve(curr, k):
    global ans
    i = 0
    for c in range(26):
        nxt = []
        while i < len(curr):
            s = curr[i]
            if len(s) <= k:
                i += 1
                continue
            if ord(s[k]) - ord('a') == c:
                nxt.append(s)
                i += 1
            else:
                break
        M = len(nxt)
        if M >= 2:
            ans += M * (M - 1) // 2
            solve(nxt, k + 1)

N = int(input())
S = input().split()
S.sort()
ans = 0
solve(S, 0)
print(ans)

再帰関数を使っていますが、PyPy でも AC になります。


あとがき

今回はABCD 4完 81分 で 2966位、1051 perf でした。

A - E でC問題がいちばん難しかったまである。
問題文を見て「ABC351 - Eでつまづいたやつだ!」となったのと同時に「あれ青diffだったよね…?」ともなりました。
ABC351 - Eを解いてなければ今回の C も解けてなかったと思います。

ではまた~。


【更新履歴】
2024/05/12 (13時頃):記事投稿。
2024/05/12 (17時頃):E問題の掲載コード、一部文章を修正。

この記事が気に入ったらサポートをしてみませんか?