Processing math: 100%

三井住友信託銀行プログラミングコンテスト2019 C~F問題メモ

Sumitomo Mitsui Trust Bank Programming Contest 2019

出張先からの参加。ラップトップのコンテスト環境がまだ整えられてないのでとっとと整えるべき。

C - 100 to 105

問題

  • 商店では、100105 円の6種類の品物が売られている
  • 合計金額をちょうど X 円にできる買い方が存在するか判定せよ
  • 品物は各種沢山あり、品切れにはならない
  • 1X100000

解法

100円を買えるだけ買ったとして、後から適宜101~105円に置き換えて調整する。

X/100=dm として、100円を d 個買って、残り m 円を調整するのに d 個では足りなかったらアウト。なので、交換個数はなるべく少なくしたい。

よって調整の仕方は、105円に置き換えられるだけ置き換えて、最後の端数があったら101~104円を1個だけ置き換えればよい。

100円から105円への置き換えで5円詰められるので、d 個で調整できる範囲は 5d まで。 m5d ならよい。

1
2
3
x = int(input())
d, m = divmod(x, 100)
print(1 if 5 * d >= m else 0)

D - Lucky PIN

問題

  • '0'~'9'からなる文字列 S が与えられる
  • 長さ3の部分列(連続でなくてもよい)は何種類あるか?
  • 同じ数字の並びは重複しては数えない
  • 4|S|30000

解法

長さ1、長さ2、長さ3 でそれぞれ取れる部分列の集合を先頭から更新していく。

それぞれの集合をL1,L2,L3とする。 集合には、重複を許さないデータ構造を使う。

i 番目の数字を c として、i1 番目までの処理が終わっている L1,L2,L3 に対して、以下をすればよい。

  • L2の各要素に c を付け加えたものをL3に加える
  • L1の各要素に c を付け加えたものをL2に加える
  • c をL1に加える
S    123123      L1      L2                  L3
i=0  1          {1}
  1   2         {1,2}    {12}
  2    3        {1,2,3}  {12,13,23}          {123}
  3     1       {1,2,3}  {12,13,23,11,21,    {123,121,131,231}
                          31}
  4      2      {1,2,3}  {12,13,23,11,21,    {123,121,131,231,122,
                          31,22,32}           132,232,112,212,312}
  5       3     {1,2,3}  {12,13,23,11,21,    {123,121,131,231,122,
                          31,22,33}           132,232,112,212,312,
                                              133,233,113,213,313,223,323}

最終的に、L3の長さが答え。

L2からL3へ移すところが計算量的にボトルネックとなる。 文字の種類(今回は10種)を D とすると、計算量は O(D2N)

1
2
3
4
5
6
7
8
9
10
11
12
13
n = int(input())
s = input()
d1 = set()
d2 = set()
d3 = set()
for c in s:
    for t in d2:
        d3.add(t + c)
    for t in d1:
        d2.add(t + c)
    d1.add(c)
    print(d1, d2, d3)
print(len(d3))

解法2

'000'~'999' それぞれについて可能か調べる。解説pdf1個目の解法。

'312' が可能か調べたければ、以下のようにする。各過程で、目標の数字が存在しなければ'312'は不可能。

  • 先頭から最初に出現する'3'の位置を得る
  • それ以降で次に最もはやく出現する'1'の位置を得る
  • それ以降に'2'が存在したら、OK

これを高速に実装するには、各数字につき、何番目に出現するかのindexリストをまず作成しておく。

  • 数字 k に対してのindexリストを lock とする
  • 数字 k に対して最後に出現するindexを lastk とする
S  123123
↓
loc1: [0, 3]    last1: 3
loc2: [1, 4]    last2: 4
loc3: [2, 5]    last3: 5
  • 1桁目の数字 x を10通り試す
    • i=locx[0] が最初のindex
    • 2桁目の数字 y を10通り試す
      • i+1 以降で最小のindexを locy から二分探索で得る。これを j とする
      • 3桁目の数字 z を10通り試す
        • j<lastz なら、部分列 'xyz' は作れる

計算量は、O(N+D2(D+logN)) となる。N が大きければ、だいたい最初の O(N) のindexリストの作成が一番時間のかかる処理となる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from bisect import bisect
 
n = int(input())
s = input()
location = [[] for _ in range(10)]
for i, c in enumerate(map(int, s)):
    location[c].append(i)
last_indices = [loc[-1] if loc else -1 for loc in location]
 
ans = 0
for loc_x in location:
    if not loc_x:
        continue
    i = loc_x[0]
    for loc_y in location:
        ji = bisect(loc_y, i)
        if ji >= len(loc_y):
            continue
        j = loc_y[ji]
        ans += sum(j < li for li in last_indices)
print(ans)

あと、Pythonでは文字列検索が十分速いため、下手にindexを前計算するより、1文字ずつ検索する方が、この制約下では速かったりする。

E - Colorful Hats 2

問題

  • N 人の人が一列に並び、前から順に 1,2,3,...,N と番号が付けられている
  • それぞれの人は、赤・青・緑のいずれかの帽子を被っている
  • 番号 i の人は「自分より前に、自分と同色の帽子を被っている人は Ai 人いる」と発言した
  • 全ての人の発言が正しくなるような N 人の帽子の色の並びとして考えられるパターン数を求めよ
  • 1N105

解法

DP。

i 人目まで見て、k 人が被っている色はいくつあるか?」を更新していく。最初は k=0 が3つ。

i 番目の人の発言が Ai 人とすると、i1 人までで被っている人数がちょうど Ai 人の色がないと達成不可能。 さらに i 番目の人が同じ色なので、その色の帽子は i 人目を加えると Ai+1 個となる。

Ai 個ある帽子が複数ある場合はどれを選んでもよいので、その数だけパターン数は乗算される。

    k= 0  1  2  3    Ans
Ai     3               1
 0     2  1            3
 0     1  2            6
 1     1  1  1        12
 0        2  1        12
 1        1  2        24
 2        1  1  1     48

1
2
3
4
5
6
7
8
9
10
11
n = int(input())
aaa = list(map(int, input().split()))
MOD = 10 ** 9 + 7
dp = [0] * (n + 1)
dp[0] = 3
ans = 1
for a in aaa:
    ans = ans * dp[a] % MOD
    dp[a] -= 1
    dp[a + 1] += 1
print(ans)

F - Interval Running

問題

  • 一直線の無限に続くランニングコースをAさんとBさんが同じ位置からスタートする
  • Aさんは最初の T1 分間、分速 A1 [m]で走り、次の T2 分間、分速 A2 [m]で走り、これを交互に繰り返す
  • Bさんは最初の T1 分間、分速 B1 [m]で走り、次の T2 分間、分速 B2 [m]で走り、これを交互に繰り返す
  • AさんとBさんが出会う(同じ位置に来る)回数は何回か求めよ
    • スタート地点は除く
    • 無限回出会う場合は'infinity'と出力する
  • 1Ti105
  • 1Ai,Bi1010
  • A1B1
  • A2B2

解法

変な読み違いをして時間をかけてしまった。速度だけでなく、時間周期 T1,T2 も、AさんとBさんで別々だと思い込んでしまった。(まぁそれでも変曲点ごとに分解すれば解ける。実装がやたら面倒くさいが)

場合分け。F問題にしては簡単? 速度と距離のグラフを書くと理解の整理になる。

周期が T1+T2 で共通(これを λ とする)なので、λ で進む距離が等しければ、λ ごとに必ず出会い続けるので、infinity。

そうでない場合、より多くの距離を進む方をBさんとする。(逆の場合、適当にひっくり返す)

Aさんが λ で進む距離(=A1T1+A2T2)を At、Bさんを Bt とする。1周期でつく距離差を Dd とする。

A1<B1の場合

Aさんは T1 で出遅れ、その差は At<Bt なので挽回できることはない。よって0回。

A1>B1の場合

Aさんが先行するが、T2 の領域で抜かされる。

T1,T2 の各領域で「何周期先まで、この領域で交わり続けるか?」を考える。

2周期目では、Bさんは Dd だけ進んだ位置からスタートしていると見なせる。3周期目では 2Dd、4周期目では 3Dd。。。

距離
|           /           |      |      |    /|
|          /            |      |      |   / |
|      __-/~~           |      |  __  |_-/~~|
|  A /  /              |  A /|  ↑d | /   |
|  /   / B             |  / B|  ↓  |/    |
|/_--~~                |/_--~|  ~~  |     |
|------|------|時間     |------|      |-----|      
   T1     T2           T1           T2

ここで T1 終了時点の両者の差を d とすると、

  • 領域 T1 では dDd(切り捨て)周期先まで交わることができる
  • 領域 T2 でも dDd(切り捨て)周期先まで交わることができる

ただし、T2 領域では1周期目でも交わっているので、+1 される。

また、d がちょうど Dd で割り切れる場合、最後の1回の接触が領域1と2で重複して数えられてしまうので、注意する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def solve(t1, t2, a1, a2, b1, b2):
    at = a1 * t1 + a2 * t2
    bt = b1 * t1 + b2 * t2
 
    if at == bt:
        return 'infinity'
    if at > bt:
        a1, a2, b1, b2 = b1, b2, a1, a2
        at, bt = bt, at
     
    ans = 0
    if a1 > b1:
        dd = bt - at
        ans += ((a1 - b1) * t1 - 1) // dd
        ans += (a1 - b1) * t1 // dd + 1
    return ans
 
 
t1, t2 = map(int, input().split())
a1, a2 = map(int, input().split())
b1, b2 = map(int, input().split())
print(solve(t1, t2, a1, a2, b1, b2))

programming_algorithm/contest_history/atcoder/2019/1201_sumitrust2019.txt · 最終更新: 2019/12/03 by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0