CODE FESTIVAL 2018 qual A
A - 配点
問題
- 問題を3問作り、配点を決めようとしている
- 以下の条件を満たす配点にできるか、判定せよ
- 1問目は A 点または A+1 点
- 2問目は B 点または B+1 点
- 3問目は C 点または C+1 点
- 3問の合計は S 点
解法
合計は (A+B+C)~(A+B+C+3) の間の任意の値を取れる。この中に S が入っていればよい。
1 2 3 4 5 |
a = int ( input ()) b = int ( input ()) c = int ( input ()) s = int ( input ()) print ( 'Yes' if a + b + c < = s < = a + b + c + 3 else 'No' ) |
B - みかん
問題
- N 個のみかんがあり、1~N の番号が付いている
- それぞれの房の数は、ちょうど A または B である
- A<B
- M 個の情報がある
- i 個目の情報では「li~ri のみかんの房は A 個である」ことがわかる
- 全てのみかんの房の数の合計として、考えられる最大値を求めよ
- 1≤N,M≤100
解法
未確定の房は B であった方が明らかに合計が大きくなるので、最大値を求める上では B と考えてよい。
制約が小さいので、シミュレーションする。
N 要素の配列を用意して、B で初期化する。
それぞれの情報で li~ri のみかんを A にする。
1 2 3 4 5 6 7 |
n, m, a, b = map ( int , input ().split()) mikan = [b] * n for _ in range (m): l, r = map ( int , input ().split()) for m in range (l - 1 , r): mikan[m] = a print ( sum (mikan)) |
C - 半分
問題
- 数列 A={a1,a2,...,aN}
- 以下の操作をちょうど K 回行う
- A の1要素を選び、2で割る。余りは切り捨てる
- K 回の操作後の A の状態としてあり得るものの数を、mod109+7 で答えよ
- 1≤N≤50
- 0≤ai≤1018
- 0≤K≤109
解法
- 0は2で割っても0のまま
- 他の数は2で割ると、数字が変化し、0までの必要回数が1回減る
- ai を2で割って0にするために必要な回数を cnti とすると、
- ai=0 の時は0
- それ以外は log2ai+1(小数点以下切り捨て)
これらを踏まえ、以下のように問題は言い換えられる。
- 石の山が N 個あり、それぞれ cnt1,cnt2,...,cntN 個の石が積まれている
- 以下の操作をちょうど K 回行う
- 山を1つ選び、石が残っていたら1個取る。残ってなかったら何もしない
- K 回の操作後、各石山に石が何個残っているかの状態数を求める
実際にやってみると、以下のことが分かる。
- 0個の石山が存在すると、それを選べば状態を変化させること無く何回でも操作を行える
- 0個の石山が存在しないと、どれかから1個取って状態を変化させざるを得ない
まず思いつくDP
解法としてDPと当たりを付け、dpi,k を「1~i 番目までの石山だけに注目して、k 回操作した後の状態数」としたとする。
だがこのままでは、なかなか遷移が難しい。実際に簡単な例で試してみる。
石山の初期状態 : {1, 2, 3} i=1 iまでの石山の初期状態: {1} 0回操作で {1} の1通り 1回(以上)操作で {0} の1通り i=2 iまでの石山の初期状態: {1, 2} 0回操作で {1, 2} の1通り 1回操作で {0, 2}, {1, 1} の2通り 2回操作で {0, 2}, {0, 1}, {1, 0} の3通り 3回操作で {0, 2}, {0, 1}, {1, 0}, {0, 0} の4通り 4回以上の操作ではずっと4通り i=3 iまでの石山の初期状態: {1, 2, 3} 操作回数 0 1 2 3 4 5 6以上 パターン数 1 3 6 10 14 17 18
どういう計算で遷移させればよいか? 法則性が見えづらい。
ややこしくしている原因は0個の石山の扱いにあって、
- 遷移
- dpi,k の中で0個の石山が含まれている状態数は、そのまま dpi,k+1 に引き継ぐことができる
- dpi,k の中で0個の石山が含まれていない状態数は、引き継ぐことができない
- 空間・計算量
- dpの計算量は O(N×(kの上限))
- 全ての石山が0になっても操作は続けられるので k の上限は K となるが、K≤109 なのでとても間に合わない
- ただ、これは回避する方法がある(後述)
改良版DP
もう一つDPに区別を設け、dpi,k,j とし、j を0か1の値を取る「0個の石山が含まれるかフラグ」とすれば遷移はやりやすそう。
また区別することで「各操作で、0個の石山は選ばない(常にどこかから石を取る)」とルールを設けてもよくなり、これにより空間の問題も解消する。
どういうことかというと、k 回の操作後に0個の石山が含まれている場合は、k+1,k+2,... 回の操作後の全ての場合においてその状態が実現可能。なので K=k+1 の時の答えを求めたければ、K=1~k の、0個の石山を含む状態数から取ってこれるので、dp(k+1)自身が持つ必要が無い。
これにより考慮すべき k の上限が cnti の総和までとなり、各 cnti は log21018=60 程度までなので、N=50 でも3000程度となり、十分小さくなる。
石山の初期状態 : {1, 2, 3} 便宜上、dp(i,k,j) の j=0をdp0, j=1をdp1と表現する i=1 k __0__1___ dp0 : 1 0 # 0個の石山を含まない場合 dp1 : 0 1 # 0個の石山を含む場合 0回の操作後は {1} の、0個の石山を含まない場合が1通り 1回の操作後は {0} の、0個の石山を含む場合が1通り 2回以上は操作を行えない i=2 k __0__1__2__3__ dp0 : 1 1 dp1 : 1 2 1 1回の操作後は {0, 2}, {1, 1} それぞれ0個の石山を含む場合と含まない場合が1通りずつ 2回の操作後は {0, 1}, {1, 0} 0個の石山を含む場合が2通り ※{0, 2} も元のルールでは2回の操作後に実現可能だが、 0個の石山に対して操作しないとならず、 1回の操作後で既にカウントされているのでここでは考慮しない 3回の操作後は {0, 0} 0個の石山を含む場合が1通り i=3 k __0__1__2__3__4__5__6__ dp0 : 1 2 2 1 dp1 : 1 3 5 5 3 1 各操作後の状態は省略 K=3の時の状態数を求める時は、 dp0からはそのまま取ってきて1通り。 dp1からは、3以下の状態数を合計して 1+3+5=9通り。 計10通り
遷移
いずれの遷移も、dpi の状態のみから dpi+1 を決定できる。
dp0 からの遷移
dp0i,k からは、dp0i+1 と dp1i+1 の両方に遷移する。
まず、dp0i+1,k+q(ただし q=0,1,...,cnti+1−1)に状態数は引き継がれる。
これは、i+1 個目の石山を、0個減らした場合、1個減らした場合、……、cnti+1−1 個減らした場合を意味している。石山は0にしてはいけないのでその直前まで。
また、dp1i+1,k+cnti+1 に引き継がれる。
これは、i+1 個目の石山に cnti+1 回操作を加えて0にした場合を意味している。
dp1 からの遷移
dp1i,k からは、dp1i+1 に遷移する。
dp1i+1,k+q(ただし q=0,1,...,cnti+1)に引き継がれる。
これは、i+1 個目の石山を、0個減らした場合、1個減らした場合、……を意味している。こちらは0まで減らしてもよい。
余談: 改良前のDPでKが大きい時の回避方法
K が cnti の総和(cnt1+cnt2+...+cntN)以上の場合、つまり、全ての石山を0にするのに十分な場合、簡単に計算できる。cnti の総和以上の K では、既に取り得る全ての状態を数え上げているので、それ以上 K が増えても答えが増えることはない。
i 個目の石山のみに着目すると、0,1,2,...,cnti の cnti+1 通りの状態を取れる。
全ての状態数(仮)は、(cnt1+1)×(cnt2+1)×...×(cntN+1) となる。
ただしこの中には、K 回の操作では不可能なものもある。それは全ての石山が0でない状態。K 回操作すると、少なくとも1つの石山は必ず0にせざるを得ない。逆に少なくとも1つの石山が0であれば、K 回の操作後でも可能となる。
よって、状態数(仮)から全ての石山が0でない cnt1×cnt2×...×cntN 通りを除けば、答えが求まる。
この枝刈りをしておけば、保持すべき K の上限は cnti の総和までとなり、空間の問題については解決する。(まぁ結局遷移が難しいんですけど)
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 |
def dividable(a): if a = = 0 : return 0 return len ( bin (a)) - 2 def solve(n, k, aaa): ddd = list ( map (dividable, aaa)) s = sum (ddd) if k > = s: ans = 1 exc = 1 for d in ddd: ans * = (d + 1 ) ans % = MOD exc * = d exc % = MOD return (ans - exc) % MOD dp0 = [ 1 ] dp1 = [ 0 ] for d in ddd: ndp0 = [ 0 ] * ( len (dp0) + d) ndp1 = [ 0 ] * ( len (dp0) + d) for i, (p0, p1) in enumerate ( zip (dp0, dp1)): for q in range (d): ndp0[i + q] + = p0 ndp1[i + q] + = p1 ndp1[i + d] + = p0 + p1 dp0 = ndp0 dp1 = ndp1 print (dp0) print (dp1) return (dp0[k] + sum (dp1[:k + 1 ])) % MOD MOD = 1000000007 n, k = map ( int , input ().split()) aaa = list ( map ( int , input ().split())) print (solve(n, k, aaa)) |
D - 通勤
問題
- 1次元上の座標 0 から D まで車で移動する
- 車のガソリンタンクは出発時、満タンで F リットル入っている
- 座標1移動するのに1リットル消費する
- N 個のガソリンスタンドが座標 0<x1<x2<...<xN<D にある
- ガソリンスタンドの前を通過する時、以下のルールでガソリンを補充する
- ガソリンの残りが T リットル以上あればスルー
- それ未満ならそこで満タンまで補充
- いくつかのガソリンスタンドを取り壊したい
- D までたどり着けるような取り壊し方は、何通りあるか、mod109+7 で答えよ
解法
i 番目のガソリンスタンドを GSi と表す。
GSi で補充後、次に補充できる可能性のあるGSは、座標 xi+(F−T)<xk≤xi+F にある GSk となる。
FULL NEXT NEXT NEXT ↓ x1 --- x2 x3 x4 ------ x5 -- x6 -- x7 ----- x8 <--- F-T ---> <----------------- F ----------------->
DPを考える。dpi=「GSi で補充することになるような、GS1,GS2,...,GSi−1 の取り壊され方のパターン数」とする。
上の例で、GS1 で補充し、次に補充するGSを考える。
- GS2,GS3,GS4 は残ってても壊されてても通過するので関係ない
- dp1 から dp5 へは、通過した3箇所分の取り壊され方が自由のため 23 倍して遷移する
- dp1 から dp6,dp7 へも同様
- GS1 の次に GS6 で補充するためには GS5 は取り壊されていないといけないので、そこでパターン数が増える余地は無い
これを先頭からDPで配っていけばよい。座標0の出発地点を GS0 と見なし、dp0=1 としてそこから始める。
答えの数え上げは、最後に給油することになりえる(つまり、xi+F≥D)各 GSi につき、残ってても通過するGSの数 t を求め、dpi×2t を加算する。
最後に給油 ↓ xi --- x x x -------- x x x ----- D <----- F-T -----> <-------------------- F -----------...-> `---------------'|`--------------' 残ってても通過する|xiを最後の給油地点にするためには GSは3個 |必ず取り壊されてないといけない ANS += dp[i] * 2^3
実装は、区間への足し込みを行うため、BITなどを使って累積和で管理する。が、もっと速い提出があるのでよりよい方法はありそう(たぶんbisectで探してる部分は尺取り法でいける)。BITではMODを取ることに注意。
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 bisect class Bit: def __init__( self , n): self .size = n self .tree = [ 0 ] * (n + 1 ) def sum ( self , i): s = 0 while i > 0 : s + = self .tree[i] i - = i & - i return s % MOD def add( self , i, x): while i < = self .size: self .tree[i] + = x self .tree[i] % = MOD i + = i & - i MOD = 1000000007 d, f, t, n = map ( int , input ().split()) xxx = [ 0 ] + list ( map ( int , input ().split())) r = f - t dp = Bit(n + 2 ) dp.add( 1 , 1 ) dp.add( 2 , - 1 ) reachable = d - f ans = 0 for i, x in enumerate (xxx): i + = 1 s = dp. sum (i) if s = = 0 : continue ti = bisect.bisect(xxx, x + r, lo = i) fi = bisect.bisect(xxx, x + f, lo = ti) inc = s * pow ( 2 , ti - i, MOD) % MOD if ti < fi: dp.add(ti + 1 , inc) dp.add(fi + 1 , - inc) if x > = reachable: ans + = inc ans % = MOD print (ans) |