1 of 67

ゆるふわ競技プログラミングオンサイト at FORCIA

#3 writer料は初任給に含まれます 解説

2 of 67

問題一覧 (Div 1)

問題名 (クリックすると当該問題の解説に飛びます)

配点 (部分点)

想定解

300

set

400

組合せ

500 (300)

LCA (DFS or BFS)

600

Grundy数

700 (400)

セグメントツリー

800

二項係数

500

パズル

3 of 67

問題一覧 (Div 2)

問題名 (クリックすると当該問題の解説に飛びます)

配点 (部分点)

想定解

100

if文

200

for文・if文

300

set

400

全探索

500

組合せ

600 (400)

LCA (DFS or BFS)

600

累積和 or DP

4 of 67

Yurufuwa Division

writer: tempura0224

5 of 67

問題概要

・レーティングが X 未満ならDiv. 2、X 以上ならDiv. 1 に参加する。

・レーティングが A の人はどちらに参加する?

6 of 67

考察

・XとAを受け取って AがX未満なら2, そうでないなら1を出力すればよい

・ということでPythonで書くとこう

  X, A = map(int,input().split())

  if A < X : print(2)

  else : print(1)

7 of 67

統計

・AC率(AC数/提出数)

  57/64 (89%)

・FA

  全体 :bayashiko(0:55)

  オンサイト :bayashiko(0:55)

8 of 67

Coupons

writer: Noimin

9 of 67

問題概要

・割引金額が決まっているクーポンを N 枚持っている

・P 円のピザは最安何円で買える?

・使えるクーポンは高々1枚で,払う金額が負になるクーポンは使えない

10 of 67

考察

・N 枚のクーポンを1枚ずつ見ていけばよい

・割引額 ci が P 円を超えるクーポンは無視

・P円以下の場合は P - ci で実際に払う金額を求め,最小値を取る

・すべてのクーポンについて ci > P が成り立つ場合に注意 (答えは P 円になる)

11 of 67

実装例 (Python)

n, p = map(int, input().split())

c = tuple(map(int, input().split()))

ans = p

for ci in c:

if ci <= p:

ans = min(p-ci, ans)

print(ans)

12 of 67

統計

・AC率(AC数/提出数)

  44/69 (64%)

・FA

  全体 : otamay (3:07)

  オンサイト : bayashiko (3:23)

13 of 67

New Comers

writer: tempura0224

14 of 67

問題概要

・第1回〜第3回の参加者名簿が与えられる

・第3回が初参加な人は何人いる?

・各回の参加者の人数<=50000

15 of 67

想定TLE解

・第3回の参加者を1人1人見ていって、第1回にも第2回にも参加していなければ

 初参加

・「第3回の参加者それぞれについて、第1回、第2回の参加者を順に見て

 同じ名前の参加者がいたらだめ」というコードを書くと、

 最悪ケースで1人調べるごとに10万人と比較しないといけないのでTLE

16 of 67

想定解

・setを使いましょう。

 - 要素の挿入、削除、検索などが高速にできるデータ構造

・第1回、第2回参加者のsetを用意して、第3回の参加者がどちらのsetにも入って

 いなければ初参加。

・これでO(NlogN)とかO(N)とかになって間に合う。

17 of 67

実装例

A, B, C = map(int,input().split())

First = set(input().split())

Second = set(input().split())

Third = list(input().split())

ans = 0

for p in Third :

 if p not in First and p not in Second : ans += 1

print(ans)

18 of 67

統計

・AC率(AC数/提出数)

  Div. 1  91/103 (88%)  

・FA

  Div. 1

  全体 :kotamanegi(1:06)

  オンサイト :n_fuppy(1:14)

 Div. 2  45/50 (90%)     

 Div. 2

 全体 :otamay(5:22)

 オンサイト :CKPCKP(6:23)

19 of 67

Books Rotation

writer: Noimin

20 of 67

問題概要

・H×W の行列が与えられる

・次の操作を何回でもできる: ある列を選び,その列の各要素を1個下に移動させる。ただし一番下の要素は一番上に移動させる

・各行に同じ数字が揃うようにできるか?

1 2 1 1 1 1 1 1

2 3 2 2 2 2 2 2

3 1 3 3 3 3 3 3

2 列目を操作

21 of 67

考察 1

・以降,解説の都合上 0-indexed で考えます

・各列 H 回操作をすれば元に戻る

・0列目以外のすべての列について,(H-1) 回以下の操作で 0列目と同じになるか? を試せばよい

1 3 2 1

2 1 3 2

3 2 1 3

22 of 67

考察 2

・x 回操作をすると元々 i 行目にいた要素は (i+x) % h 行目に動く

・つまり, i 行 j 列の値を (i+x) % h 行 0 列の値と比較すればチェックできる

・ 操作する列 ((W-1)列のどれか) と操作回数(最大 (H-1) 回) を決めたとき,   「0列目と同じになるか?」の判定が O(H) なので,全体では O(H^2 W)

・ H,W <= 100 なので間に合う

(0, 0)

(0, 1)

(1, 0)

(1, 1)

(2, 0)

(2, 1)

(0, 0)

(2, 1)

(1, 0)

(0, 1)

(2, 0)

(1, 1)

1 列目を操作

23 of 67

実装例 (Python)

(入力略)

yes = True

for j in range(1, w): # 何列目を操作するか

is_same = False

for x in range(h): # 何回操作するか

is_same = True

for i in range(h): # 0列目と揃っているかどうかのチェック

if a[(i+x)%h][0] != a[i][j]: is_same = False

if is_same: break

if not is_same: yes = False

print(“Yes” if yes else “No”)

24 of 67

統計

・AC率(AC数/提出数)

  43/59 (73%)

・FA

  全体 : otamay(10:40)

  オンサイト : shiro53(14:17)

25 of 67

Median Permutation

writer: Noimin

26 of 67

問題概要

次の3条件を満たす数列の数を 10^9+7 で割った余りを答える。

・要素数は N

・1 から N までの数が1回ずつ使われる

・奇数番目の要素はそれまでの要素すべての中央値になっている

条件を満たす数列の例: N = 5 における 5, 1, 4, 2, 3

27 of 67

考察 1

・奇数番目の要素については,後ろの要素になればなるほど条件が厳しい

→ 後ろから埋めることを考えてみよう!

例: N = 5 のとき

i

1

2

3

4

5

ai

3

i = 5 の要素は数列全体の中央値でなければならない

→ 3 しか入らない

i

1

2

3

4

5

ai

2

3

i = 4 の要素は偶数番目なのでなんでもよい

28 of 67

考察 2

例: N = 5 のとき

i

1

2

3

4

5

ai

1

4

2

3

i = 2 の要素は偶数番目なのでなんでもよい

i

1

2

3

4

5

ai

5

1

4

2

3

i = 1 の要素は i = 1 の中央値でなければならない

→まだ使っていない 5 を入れる

i

1

2

3

4

5

ai

4

2

3

i = 3 の要素は i = 1, 2, 3 の中央値でなければならない

→ まだ使っていない 1, 4, 5 の中央値である 4 が入る

29 of 67

考察 3

・ i 番目の要素を選ぶときの選択肢の数

・ i が奇数 → 1 個 (今残っている数が何かで決まる)

・ i が偶数 → i 個 (今残っている数のうちなんでもよい)

・したがって求めるべき数列の数は

・ N が奇数 → (N-1) × (N-3) × … × 4 × 2 = (N-1)!!

・ N が偶数 → N × (N-2) × … × 4 × 2 = N!!

30 of 67

統計

・AC率(AC数/提出数)

  Div. 1  83/94 (88%)  

・FA

  Div. 1

  全体 : e869120(3:28)

  オンサイト : e869120(3:28)

 Div. 2  24/28 (86%)     

 Div. 2

 全体 : y_homma46(26:37)

 オンサイト : CKPCKP(29:59)

31 of 67

Bananas Multiplier

writer: Noimin

32 of 67

問題概要

・N 頂点の木 (Bananas Multiplier) が与えられる

・各辺は重みがついていて,その辺を通るとバナナの数がその重み倍に増える

・頂点 mt にバナナを xt 本置き,頂点 pt で回収するとき,回収できるバナナの本数を 10^9+7 で割った余りは?

mt = 3, pt = 2, xt = 2 の

場合

33 of 67

考察1 (Easy)

・要するに,xt × (mt から pt に向かうパスに含まれる辺の重みの積) を計算したい

・mt から pt に向かうパスは BFS (or DFS) で求められる

・頂点数もクエリ数も 10^3 以下なので間に合う

34 of 67

考察2 (Hard)

・頂点数・クエリ数はともに 10^5 以下なので,BFS (or DFS) では TLE

・クエリのたびに BFS (DFS) を回さずに済む方法はないか?

・頂点mt, pt に対して,mt と pt をともに子孫としてもち,かつ  根から最も遠いノード vt を求めることができる

・ Lowest Common Ancestor (LCA) という

・ダブリングなどを使えば前処理 O(N log N) ・クエリ O(log N) で求めることができる

35 of 67

考察3 (Hard)

・LCA が分かれば,各頂点について「根からその頂点にたどり着くまでに通る辺の重みの積」をあらかじめ求めておくことで

xt × (mt から pt に向かうパスに含まれる辺の重みの積) を

xt × として

求めることができる

(根から mt までの重みの積) × (根から pt までの重みの積)

(根から vt までの重みの積)2

36 of 67

考察4 (Hard)

・時間計算量は,

・各頂点についてそこまでの重みの積を求める前計算で O(N)

・LCA を求めるための前計算で O(N log N)

・1つのクエリの計算で O(log N)

なので,全体で O(N log N + Q log N)

37 of 67

統計

・AC率(AC数/提出数)

  Div. 1 (hard) 78/128 (61%)

      (easy) 80/120 (67%)

・FA

  Div. 1

  全体 : hitonanode(9:19)

  オンサイト : e869120(12:49)

 Div. 2 (hard) 10/28 (36%)

     (easy) 27/48 (56%)     

 Div. 2

 全体 : otamay(27:44)

 オンサイト : shiro53(50:52)

38 of 67

Banana Game

writer: tempura0224

39 of 67

問題概要

N個の袋に激辛バナナと普通のバナナが入っている。

2人でゲームをする。以下の操作を交互に行う。

・N個の袋から1つを選んでその中から好きな本数のバナナを取り出して食べる。

 ただし激辛バナナは 1 本まで

操作ができなくなった方が負け。勝つのは先手?後手?

40 of 67

考察1

・見るからにNimっぽいルール(というか激辛バナナがなければNimと完全に同じ

 ルール)

・ということで Grundy 数を求めてみる

・Grundy数...その局面から移動できる局面のGrundy数の集合をGとしたとき、

 Gに含まれない最小の非負整数

41 of 67

考察2

実際に計算してみるとこんな感じ

規則性を見つけると

・青色部分(普通>=激辛)では

 Grundy数 = 辛 + 普

・白色部分(普通<激辛)では

 Grundy数 = 2 * 普 + (0,1 繰り返し)

辛\普

0

1

2

3

4

5

6

7

0

0

1

2

3

4

5

6

7

1

1

2

3

4

5

6

7

8

2

0

3

4

5

6

7

8

9

3

1

2

5

6

7

8

9

10

4

0

3

4

7

8

9

10

11

5

1

2

5

6

9

10

11

12

6

0

3

4

7

8

11

12

13

7

1

2

5

6

9

10

13

14

42 of 67

考察3

・前のページの式が一般に正しいことは帰納法などで証明できるので、無事 

 Grundy数を求めることができた

・各袋のGrundy数のxorを求めて0なら後手の勝ち、非0なら先手の勝ち

43 of 67

統計

・AC率(AC数/提出数)

  50/132 (38%)

・FA

  全体 :sugarrr(19:39)

  オンサイト :e869120(21:17)

44 of 67

Sweets Distribution

writer: tempura0224

45 of 67

問題概要(easy)

・N個のお菓子が1列に並んでいる。

・matsu, prd, tempura, Noimin が食べたときの美味しさは、順にAi, Bi, Ci, Di

・左から1〜i番目をmatsu, 左からi+1〜j番目をprd, 左からj+1〜k番目をtempura,  左からk+1〜N番目をNoimin が食べたときの美味しさの合計をf(i,j,k)と表す

・f(i,j,k)の最大値は?

46 of 67

考察1

・matsu, prd, tempura, Noimin を順に人1, 2, 3, 4と呼ぶことにします。

・DPできるよ!

・具体的には dp[i][j] = 1〜i 番目までのお菓子について、i番目を人jが食べると

 したときの美味しさの合計のmax

・求めるものはdp[n][4]

47 of 67

考察2

・遷移はこんな感じ

  dp[i+1][1] = dp[i][1] + A[i+1]

  dp[i+1][2] = max(dp[i][2], dp[i][1]) + B[i+1]

  dp[i+1][3] = max(dp[i][3], dp[i][2]) + C[i+1]

  dp[i+1][4] = max(dp[i][4], dp[i][3]) + D[i+1]

  

48 of 67

問題概要(hard)

・easy に swap クエリを足したもの

・Q回以下のクエリを処理する。

  l 番目と r 番目のお菓子の場所を入れ替えて、f(i,j,k) の max を求める

 

49 of 67

考察1

・Q は最大 20000 なので、毎回easyと同じDPをしていると間に合わない。

 各クエリに高速に答える必要がある。

 

50 of 67

考察2

・セグ木に乗るよ!!!!

・Segment Tree の各ノードに

 a[i][j] = そのノードの左端のお菓子を人i、右端を人j が食べるときの美味しさの

 和の最大値(1<=i<=j<=4)

 を載せる

51 of 67

考察3

・ノードとノードのマージは

  a[i][j] = max( max(i<=k<=j) (al[i][k]+ar[k][j]) , max(i<=k<j) (al[i][k]+ar[k+1][j]) )

 みたいな感じでできる。

・l 番目と r 番目の交換は 1 点更新を 2 回と思えばいい。

52 of 67

考察4

・結局更新も答えを求めるのも O(logN * K ) でできる(Kはノードのマージ1回の

 計算量、だいたい30くらい)

・よって、全体の計算量は O(N*K + Q*K*logN)

・最初のセグ木の構築はO(N*K)でやりましょう(O(N*K*logN)だと間に合わない

 可能性が高いです。)

53 of 67

統計

・AC率(AC数/提出数)

  Div. 1 (hard) 18/54 (33%)

      (easy) 71/97 (73%)

・FA

  Div. 1 (hard)

  全体 :noshi91(29:04)

  オンサイト :e869120(35:51)

 Div. 2 (hard) --/-- (-- % )

     (easy) 8/14 (57%)     

 Div. 2 (easy)

 全体 :CKPCKP(57:32)

 オンサイト : CKPCKP(57:32)

54 of 67

Yet Another Cake Distribution

writer : tempura0224

55 of 67

問題概要

・ケーキを4人で切り分ける

・上、下、左、右 みたいな感じで分けたい。

・切り分け方は何通り?

56 of 67

解説書くの飽きた

( 02 / 29 04 : 24 )

57 of 67

この絵から察してください。

58 of 67

統計

・AC率(AC数/提出数)

  3/5 (60%)

・FA

  全体 :Nyaan(78:39)

  オンサイト : --- (--:--)

59 of 67

Digit Sum Multiple

writer: tempura0224

60 of 67

問題概要

・以下の条件をすべてみたす正の整数Nを見つけろ。

 1. どの桁も0でない。

 2. 各桁の和をS(N)とするとNはS(N)で割り切れる。

 3. ちょうどM桁である。

61 of 67

考察1

S(N)を決めて桁和がS(N)になるM桁の整数を探すことにしよう

S(N)をいくつにするのが簡単か?

・素数?

・合成数?

62 of 67

考察2

結論からいうと 2べきにするのが簡単だと思います。

理由:

例えば、10000 = 10^4 の倍数は 2^4 の倍数である。

つまり、下4桁さえ16の倍数に調整してしまえば、それ以外はいくつにしても絶対16の倍数になってくれる。

63 of 67

考察3

よって、2^x が M より少し大きめになるくらいのxを取り、

(残りのM-x桁は和が2^xになるように適当に調整)(下x桁はx桁の2^xの倍数)

でM桁の整数をつくればOK

残る問題はx桁でどの桁も0でないような2^xの倍数が見つかるかどうか

64 of 67

考察4

あります!!!

11211111212122112

k=1〜17について、下k桁が2^k の倍数であるような整数

2^17 = 131072 > 100000 なので今回の制約ではこれで十分(実は任意のnについてn桁でどの桁も1または2である2^nの倍数が存在することが証明できます)

65 of 67

まとめ

結局以下のように構成すればOK

M<=2 → 適当に埋め込み(下の方法だとちょっとバグるので)

それ以外→2^x が M より少し大きめになるくらいのxを取り、

(残りのM-x桁で和が2^xになるように調整)(11211111212122112の下x桁)

66 of 67

統計

・AC率(AC数/提出数)

  13/49 (27%)

・FA

  全体 :yosupo(25:12)

  オンサイト :e869120(55:20)

67 of 67

🏅表彰🏅