Processing math: 100%

CODE FESTIVAL 2018 Final A~E

CODE FESTIVAL 2018 Final (Parallel)

オンサイト以外解説はなし?

A - 2540

問題

  • N 頂点 M 辺のグラフ
  • i 番目の辺は AiBiを長さLiで結ぶ
  • 以下の条件を満たす頂点の組(a,b,c)の個数を求めよ
    • ab間とbc間の両方に辺があり、2本の長さの合計は2540である
    • acは異なり、a<c である
  • 自己辺や二重辺はない

解法

グラフの問題っぽいが、求める内容が限定的なので、情報の持たせ方も簡単にできる。

各頂点に、そこから伸びる辺の長さと、その個数を記録していく。具体的な頂点番号は必要ない。

辺を1本ずつ調べ、それまでに調べた辺と、今の辺で、2540になるものがあれば、数え上げる。

①--1000-,  ,-1000--②            今までに調べた辺
          ③                      ③: 1000が2本,1200が1本
④--1200-'          ⑤--1000--⑥  ⑤: 1000が1本
--------------------------------------------------  
①--1000-,  ,-1000--②            ③⑤を1540で結ぶ辺を追加
          ③                      →③または⑤から伸びる2540-1540=1000の辺の個数=3本
④--1200-'  `-1540--⑤--1000--⑥  →答えに3を加算

この問題に限らないが、全体的に入力行数が多いので、input()よりsys.stdin.readlines()を使った方が速くてよい。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import sys
from collections import defaultdict
 
n, m = map(int, input().split())
ans = 0
cnt = [defaultdict(int) for _ in range(n)]
for line in sys.stdin.readlines():
    a, b, l = map(int, line.split())
    a -= 1
    b -= 1
    r = 2540 - l
    ans += cnt[a][r]
    ans += cnt[b][r]
    cnt[a][l] += 1
    cnt[b][l] += 1
print(ans)

B - Theme Color

問題

  • N人がM個の候補に投票する
    • 各人は独立に等確率で投票する
  • 票の入り方r1,r2,...,rMが指定される
    • r1+r2+...+rM=N
  • そのような票の入り方になる確率をpとした時、p>10xを満たす最小のxを求めよ

  • 3人が2候補に投票
  • r1=1,r2=2

このような票の入り方になる確率は、0.375。よって、100>0.375>101なので、最小のxは「1」

解法

中学か高校くらいでやったことあるような数学。

まず、票の入り方の確率を立式する。

分子(指定された投票のされ方)は、N!r1!r2!...rM!、分母(全ての投票のされ方)は、MN

まとめるとp=N!r1!r2!...rM!MN

これをまともに計算したらオーバーフロー一直線なので、logをとる。答えが10の冪で問われているので、log10をとる。

logをとると、かけ算は足し算に、割り算は引き算になる。

log10p=log10N!log10r1!log10r2!...log10rM!Nlog10M

0!...N!までの階乗、およびMのlog10を事前計算しておけば答えを出せる。

答えは、ガウス記号を用いて[log10p]となる。

ただ、言語や使用する関数によっては誤差が出てしまう。 Pythonでは、log(a, base)を使うと1ケースでWAだったが、log10(a)を使うとACだった。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from math import ceil, log10
 
 
def prepare(n, m):
    l = 0
    res = [0, 0]
    for i in range(2, n + 1):
        t = log10(i)
        l += t
        res.append(l)
    return res
 
 
n, m = map(int, input().split())
 
res = prepare(n, m)
 
ans = res[n] - log10(m) * n
for r in map(int, input().split()):
    ans -= res[r]
 
print(ceil(-ans))

C - Telephone Charge

問題

  • ある携帯会社では料金プランがN個ある
  • プランiの通話料金は、ひと月の通話時間がAi分以内ならBi円、超過分は1分あたり1円
  • M人の人がいて、i番目の人のひと月の通話時間はTi
  • それぞれの人について、最安プランを選択した時の通話料金を求めよ
  • あるプランが、他のプランの上位互換となることはない
    • つまり、ひと月の通話料金T=Aiの時、最安プランはiで、他より1円以上安い

解法

超過分はどのプランも一律1円であることと、最後の条件が重要で、この2つのおかげで調べる範囲を限定できる。

通話時間がTなら、Tに近いAiを持つiが、最適なプランとなる。条件を考えると、Ai=Ajとなるi,jは存在しない。

料|
金|         /      /   /
  |A-------/------/---'
  |B------/------'
  |C-----'             時間
  +-------|-------|--------
   Cが最適 Bが最適 Aが最適

AiをソートしてTを二分探索し、T=Aiならそのプラン。Ai<T<Ajならi,jのいずれかが最適プランとなる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import bisect
import sys
 
n = int(input())
lines = sys.stdin.readlines()
plans = [tuple(map(int, line.split())) for line in lines[:n]]
plans.sort()
min_t = plans[0][0]
max_t = plans[-1][0]
m = int(lines[n])
buf = []
for line in lines[n + 1:]:
    t = int(line)
    if t <= min_t:
        buf.append(plans[0][1])
        continue
    if t >= max_t:
        buf.append(plans[-1][1] + t - max_t)
        continue
    i = bisect.bisect(plans, (t,))
    ans = min(plans[i - 1][1] + t - plans[i - 1][0], plans[i][1])
    buf.append(ans)
 
print('\n'.join(map(str, buf)))

D - Three Letters

問題

  • N個の3文字以上の文字列がある
    • 英大小文字52種類の文字からなる
  • 各文字列の部分文字列を考える(連続で無くてもよい)
  • 最も多くの文字列の部分文字列となる、長さ3の部分文字列を求めよ
    • 個数が同じ場合は、ASCII文字コードの辞書順で最初に来るものを求めよ
  • N個の文字列の文字数総計90000

aKIHaBaRa  => KIB, aKa, aaa, ...
aKIBa      => KIB, aKa, IBa, ...
aSaKUSa    => SKU, aKa, aaa, ...
SHINKIBA   => KIB, SNK, IIA, ...

'aKa'と'KIB'が3個でトップ。ASCII文字コードでは大文字の方が先に来るので、KIBが答え。

解法

cnt[i][j][k]の配列を用意し、文字列Si,j,kの部分文字列が存在したら、カウントしていくことを考える。(i,j,kは文字コードで考える)

さて、しかし存在するかどうかを愚直に求めると「(i,j)が出てきたかどうか」を保存し、それ以降の文字kごとに調べていくために522×|S|くらいのループが必要になり、厳しい。

ここでは、「(i,j)が最初に出てきた位置」と「kが最後に出てきた位置」を分けて保存する。文字列Sの全ての位置を保存し終えたら、出現した各(i,j),kについて位置を比較し、(i,j)の方が前なら部分文字列i,j,kが存在することになる。

これでも、Pythonだと厳しい。PyPyで何とか。とにかく配列参照が遅いので、array[i][j][k]i,j,kのループで回す時に、逐一array[i]array[i][j]を変数に保存して、参照回数を減らすなどで、TLEを回避する。

もっと良い方法はあるかも?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import sys
from string import ascii_letters
 
cnt = [[[0] * 123 for _ in range(123)] for _ in range(123)]
first_appears2 = [[-1] * 123 for _ in range(123)]  # switched
last_appears1 = [-1] * 123
appeared1 = set()
 
n = int(input())
for s in sys.stdin.readlines():
    appeared2 = []
 
    for i, c in enumerate(map(ord, s.strip())):
        fac = first_appears2[c]
        for a in appeared1:
            if fac[a] == -1:
                fac[a] = i
                appeared2.append((a, c))
        last_appears1[c] = i
        appeared1.add(c)
 
    for a, b in appeared2:
        cnt_ab = cnt[a][b]
        fab = first_appears2[b]
        i = fab[a]
        for c in appeared1:
            if i < last_appears1[c]:
                cnt_ab[c] += 1
        fab[a] = -1
 
    appeared1.clear()
 
ans = None
max_cnt = 0
ascii_orders = sorted(map(ord, ascii_letters))
for i in ascii_orders:
    cnt_i = cnt[i]
    for j in ascii_orders:
        cnt_ij = cnt_i[j]
        for k in ascii_orders:
            cnt_ijk = cnt_ij[k]
            if cnt_ijk > max_cnt:
                ans = [i, j, k]
                max_cnt = cnt_ijk
 
print(''.join(map(chr, ans)))

E - Tough Journey

問題

  • 旅人が都市0にいて、1,2,...を順に経由して都市Nまで行く
  • 旅人は空のペットボトルをK本持っている
  • 都市i0i<N)では以下の行動を取れる
    • Ai円でペットボトル1本を水で満タンにする(お金さえ払えば何本でも可)
    • ペットボトル1本を飲み干し、次の都市i+1へ移動する
  • 最安で移動した時の金額を求めよ

解法

最適な行動は決まっている。

  • 最も近い、今の都市よりも安く水を買える都市に行く。今の都市で水は、そこに到達できるぎりぎりの量だけ買う。
  • K以内に今よりも安く水を買える都市が無い場合は、今の都市でK本全て満たし、行ける範囲で最も安い都市に行く。

ここで必要なのは、「i+1i+Kで一番安い都市」を手早く求められるデータ構造となる。

これは、セグメント木で持ったり、ソートして事前計算しておくことで可能となる。

あとは上記の行動をきちんと実装すればよい。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
from heapq import heappop, heappush, heapify
from itertools import islice
 
 
def solve(n, k, aaa):
    if n == 1:
        return aaa[0]
 
    min_array = []
    q = [(a, -i) for i, a in islice(enumerate(aaa[:k + 1]), 1, None)]
    heapify(q)
    min_array.append(q[0])
    for i, a in enumerate(aaa[k + 1:]):
        j = i + k + 1
        heappush(q, (a, -j))
        while -q[0][1] <= i + 1:
            heappop(q)
        min_array.append(q[0])
    min_array.extend([min_array[-1]] * k)
    last_i = -min_array[-1][1]
 
    ans = 0
    remain = 0
    i = 0
    while i < last_i:
        a = aaa[i]
        nxa, nxi = min_array[i]
        nxi = -nxi
        need = nxi - i
        if a <= nxa:
            ans += a * (k - remain)
            remain = k - need
            i = nxi
        else:
            for j in range(i + 1, nxi + 1):
                if a > aaa[j]:
                    need = j - i
                    if need <= remain:
                        remain -= need
                    else:
                        ans += a * (need - remain)
                        remain = 0
                    i = j
                    break
 
    need = n - i
    ans += aaa[i] * (need - remain)
    return ans
 
 
n, k = map(int, input().split())
aaa = list(map(int, input().split()))
print(solve(n, k, aaa))

もしくは、以下のような解法もあるようだ。

  • 0N1の全ての都市で水を飲む必要がある
  • その水は、iKi1のどこかで購入したものである必要がある
  • 最も安い都市で水を買ったことにして、それを合計する

F - Dinner Planning

問題

解法

1
 

G - Chicks and Cages

問題

  • N羽のひよこをM個の箱に閉じ込める
  • i番目のひよこの大きさはAi
  • 各ひよこは、自分を含めて同じ箱に入っているひよこの大きさの総和のストレスを受ける
  • ひよこが受けるストレスの総和を最小化するとき、その総和を求めよ
  • 1MN2000

Ai   5 10      8  11  6
箱 └──┘  └────┘
      ↓             ↓
2羽が各15のストレス  3羽が各25のストレス
    15 x 2  +  25 x 3 = 105

(※最適では無い)

解法

1羽のひよこの大きさは、同じ箱に入っているひよこの数だけ加算される。よってストレスの総和は「Ai×(同じ箱のひよこの数)」の和で計算できる。

Ai              5  10   8  11   6
同じ箱のひよこ  2   2   3   3   3
               10 +20 +24 +33 +18 = 105

そうすると、大きなひよこが、小さなひよこより、ひよこが沢山入っている箱に入っているのは非効率。交換すれば常に総和を減少できる。

                v-----------v
Ai             11  10   8   5   6
同じ箱のひよこ  2   2   3   3   3
               22 +20 +24 +15 +18 = 99

よって、ひよこの大きさでソートし、小さい方から箱に入れていけばいい。その際、例えば小さい方から1つの箱に6羽入れたら、次の箱には7羽以上入れないようにする。

これを全通り試すのは一見計算量がかかりそうだが、単調減少の制約があるループは、調和級数の和 nk=11k=log(n+1) よりNlogNになり、意外と少なくなるらしい。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from itertools import accumulate
 
n, m = map(int, input().split())
aaa = sorted(map(int, input().split()))
acc = [0] + list(accumulate(aaa))
cache = [[0] * (m + 1) for _ in range(n + 1)]
 
 
def cal(chick, cage, k):
    if cache[chick][cage] > 0:
        return cache[chick][cage]
    if cage == 1:
        ans = (acc[-1] - acc[-chick - 1]) * chick
        cache[chick][cage] = ans
        return ans
    upper = min(k, chick - (cage - 1))
    lower = (chick - 1) // cage + 1
    ans = float('inf')
    for i in range(lower, upper + 1):
        cur_min = (acc[-chick + i - 1] - acc[-chick - 1]) * i
        cur_min += cal(chick - i, cage - 1, i)
        ans = min(ans, cur_min)
    cache[chick][cage] = ans
    return ans
 
 
print(cal(n, m, n))

H - Pothunter

問題

解法

1
 

I - Homework

問題

解法

1
 

J - Complicated Operations

問題

解法

1
 

programming_algorithm/contest_history/atcoder/2018/1117_code_festival_2018_final.txt · 最終更新: 2018/11/21 by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0