−目次
AtCoder Regular Contest 104 B,C,D,E問題メモ
めっちゃ久々のARC。
B - DNA Sequence
問題
- 長さ N の、
ATCG
のいずれかの文字からなる文字列 S が与えられる - S の連続する部分文字列のうち、以下の条件を満たすものの個数を求めよ
- 部分文字列 T1 とすると、好きに並べ替えることによって以下の条件を満たす T2 が作れる
- 全ての i=1~|T1| において、
- T1 の i 文字目が
'A
' なら T2 の i 文字目は'T
' - T1 の i 文字目が
'T
' なら T2 の i 文字目は'A
' - T1 の i 文字目が
'C
' なら T2 の i 文字目は'G
' - T1 の i 文字目が
'G
' なら T2 の i 文字目は'C
'
- 1≤N≤5000
解法
要は、「'A
'と'T
'、'C
'と'G
'をそれぞれ同数ずつ含む部分文字列」を数えればよい。
2つの要素が絡むとややこしいので、たとえばAとTしかないと単純化する。
Xi を「S を先頭から i 文字目まで見て余っているAの個数」とする。不足分は負で表す。
X0=0 で初期化し、Aが現れたら+1、Tが現れたら-1として更新していく。
A T A A T T T T X 0 1 0 1 2 1 0 -1 -2
すると、「Xi=Xj である」=「Si+1~Sj はAとTを同数ずつ含む」となる。この (i,j) のペアの個数を数えればよい。
これは、以下のような Y を作っておいて、順番に Xi を求めていく中で更新していけばよい。
- Y[j]= これまでに Xi=j となった回数
i について Xi を更新したら、その時の Y[Xi] の値が「(?,i) となるようなペアの個数」となるので、答えに加算する。
その後、Y[Xi] に+1して更新する。
Y X -2 -1 0 1 2 ペア 始 0 1 A 1 1 1 T 0 2 1 1 A 1 2 2 1 A 2 2 2 1 T 1 2 3 1 2 T 0 3 3 1 2 T -1 1 3 3 1 T -2 1 1 3 3 1 _______ Ans. 6
今回の問題では、これを2次元で行えばよい。
計算量は、イテレートする部分は O(N) なのだが、Y の二次元配列を確保するところで O(N2) かかる。
C - Fair Elevator
問題
- エレベーターが1階から 2N 階まで、1回だけ移動した
- その間、N 人の人が乗り降りした
- 2N 個のフロア全てで、乗りまたは降りが1回ずつ発生した
- エレベーターに同時に乗っていた瞬間がある人達は、移動した階(降りた階-乗った階-1)が全て同じだった
- N 人の人の乗降記録がある
- i 番目の人は Ai 階で乗り、Bi 階で降りた
- ただし、Ai=−1 や Bi=−1 の時は、記録が無いことを表す
- 乗降記録が、上記の説明と矛盾しないか判定せよ
- 1≤N≤100
解法
丁寧な条件分岐をしての全探索。
まず、互いに-1でないのに Ai≥Bi だったり、Ai,Bi の間で重複があったらアウト。
以下、そうで無いとする。「移動した階が全て同じ」という条件を考える。
仮に、ある人が1→5階へ移動したとする。2,3,4階で他の人の乗り降りが発生している。
ここで、他の人は同様に4階層移動しなければならないので、「降りた」ことはあり得ない(地下から乗ったことになってしまう)。
1 2 3 4 5 6 7 8 9 10 |---------->| |---------->| |---------->| |---------->|
結局、このようになるしか無い。
すると 1~8 階は全て埋まったため、この間ではこれ以上乗りも降りも発生しない。次は、9階が先ほどの1階と同じようにして、議論が進められる。
つまり、正しい乗降記録はある連続した偶数長の区間 [i,i+2w) に分けられて、各区間で [i,i+w) は全て乗り、[i+w,i+2w) は全て降り、となるしかない。
この偶数長の区間を、「グループ」とする。
グループの分け方について全探索し、与えられた乗降記録と矛盾しないものがあるか調べる。
左から順番に仮確定し、残りを矛盾無く確定できるか調べていく。
関数 f(L) を「L~2N を矛盾無くグループ分けできるか?」とする。以下のように調べればよい。
- L から 2w の長さのグループを作るとする
- 各 w=1~(L+2wが2Nを越えないギリギリ) について
- 各 k=0~w−1 について、
- 以下の条件のいずれかに当てはまれば、L からの長さ 2w のグループは不可能
- L+k 階で降りた人が乗降記録にある
- L+k+w 階で乗った人が乗降記録にある
- L+k 階で乗った人と L+k+w 階で降りた人が乗降記録上で異なる
- もし長さ 2w のグループが可能なら、仮確定し、f(L+2w) へ進む
- L=2N+1 まで到達したら成功
f(L) は 1~L−1 の分け方に関わらないので、メモ化で高速化できる。
これ、嘘解法ですね……。同じ人が2回乗っても許されちゃう。
2 1 4 -1 -1 Noが正しいが、Yesになる
「L+k 階で乗った人が指定されていて、さらにその人の降りた階も指定されている場合、L+k+w がその階で無ければ不可能」という判定も必要。
D - Multiset Mean
問題
- 正整数 N,K,M が与えられる
- x=1~N のそれぞれについて、以下を求めよ
- 1~N の整数をそれぞれ 0 個以上 K 個以下含む、空で無い多重集合であって、平均が x であるものの個数を M で割った余り
- 1≤N,K≤100
- 108≤M≤109+9
- M は素数
例
N=5,K=3 で x=4 について求める場合、1~5を上限3個まで含む、平均が4の集合の個数。
{4}, {3,5}, {1,5,5,5}
などが該当し、全部で27個存在する。
解法
DPなんだけど、上手な高速化が必要となる。
方針
たとえば N=8 で x=4 について求めたいとすると、全体から 4 を引いて考え、平均を0にすると考えるとやりやすい。
1 2 3 4 5 6 7 8 ↓ -3 -2 -1 0 1 2 3 4
すると、以下のような値を事前計算しておくと、
- DP[i][j]=1~i をそれぞれ上限 K 個まで含む多重集合で、総和が j となるものの個数
平均が0となるには、0 より左(−1~−3)の総和と、0 より右(1~4)の総和が打ち消し合ってくれればよいので、以下のように求められる。
- ∑j=0(DP[3][j]×DP[4][j])
さらに 0 自身も 0~K 個含んでいてよいので K+1 倍、ただし空集合が含まれるのでそれは除き、最終的に以下が答えとなる。
- ∑j=0(DP[3][j]×DP[4][j])×(K+1)−1
DPの求め方
いざDPを求めようとすると、これが一筋縄ではいかないことに気付く。
というのも、DP[i][j] において i,j の範囲は以下のようになる。
- i: x=1 の時などが最小と最大を同時に必要とし、0≤i≤N−1
- j: 和の最大値は、i が最大値 N−1 の時、(1+2+...+N−1)×K=N(N−1)2K
つまり、DP の必要とする空間サイズは O(N3K) となる。
そして、この更新は、愚直にやろうとすると骨子は以下のようになる。(添字が範囲外になる場合などは適宜除外する)
DP[0][0] = 1 for i in range(1, N): for j in range(0, N*(N-1)*K//2+1): for t in range(0, K): DP[i][j] += DP[i-1][j-i*t]
これは全体で O(N3K2) のループとなり、枝刈りなどをしてもさすがにTLE。1)
AtCoder-Libraryのconvolutionでも使うのかな? と思ったが、それでも O(N3KlogN2K) であり、効果は薄い。 また、任意MODでのconvolutionは逆元を求めるフェイズで M の値によって多少の時間がかかってしまう場合があり、 わざわざ問題設定で M を任意にしているということは「convolutionじゃないぞ」というメッセージのような気がする。
解説を見ると、以下のような方法を用いて高速化している(難しいことはしていないのだが)。
- j について順に処理するとき、i 個周期で参照する箇所の多くが被ることになるので、その情報を上手く使う
具体的には、以下のようになる。
※DP[i-1] を単にDP1と表記する DP[i][j] = DP1[j] + DP1[j-i] + DP1[j-2i] + ... + DP1[j-Ki] DP[i][j-i] = DP1[j-i] + DP1[i-2i] + ... + DP1[j-Ki] + DP1[j-(K+1)i]
この差分だけを足し引きすればよい。
これで、DP の構築が O(N3K) で収まる。
E - Random LIS
問題
- 整数列 A1,A2,...,AN が与えられる
- ここから長さ N の整数列 X を作る
- 各 i=1~N に対して、Xi は独立に 1~Ai の中から一様ランダムで選ばれる
- この X の最長増加部分列(狭義単調増加)の長さの期待値を求めよ
- 1≤N≤6
- 1≤Ai≤109
解法
制約が独特。
分数は考えるのが面倒なので、「全ての作り得る X に対してLISの総和」を求めて、後から作り得る X の個数で割ることにする。 作り得る X の個数は単純に A1×A2×...×AN である。
通常のLISは、「DP[i][j]= A1~Ai だけで長さ j のLISを作るのに必要な最小の末尾要素」として前から順に更新すると解ける。
これを拡張して、試しに以下にしてみたらどうだろうか、と考えたが、
- DP[i][j][k]= A1~Ai だけで長さ j のLISを作るのに必要な最小の末尾要素が k になる個数
これは最終的にDPを埋められたとしても、結局、ある j が最終的なLISの長さとしてあり得るのか、途中経過にのみ出現するのか、区別することができない。
想定解は、N≤6 という制約を利用して、X における N 個の数の大小関係を全て網羅してしまえばよいらしい。
たとえば、N=4 なら (2,0,0,1) は、X2=X3<X4<X1 となるような X を示す。
大小関係さえ分かれば、それに対する「LISの値」と「パターン数」をそれぞれ個別に求められるので、漏れなく全パターンの総和を求めることが可能となる。
大小関係の列挙方法
大小関係 D を、(2,0,0,1) のように 0~k−1 からなる重複ありの長さ N の数列で表現するとする。
全探索するにあたり、NN 通りを調べてもよいが、たとえば (2,0,0,1) や (3,0,0,2) や (3,1,1,2) は全て同じ関係なので、これらを重複して数えないようにしなければならない。
先に何通りの値が出てくるか k=1~N−1 を決めてしまって、kN 通りの数列を全て調べる。この内、使われている値の種類が k に満たないものは除外すると、重複無く数えられる。
k=N の時は順列を全列挙すればよいので、上記の方法は k=N−1 まででよい。
使われている値の種類を調べるのにも N かかるとして、列挙にかかる計算量は N(1N+2N+...+(N−1)N)+N! に比例し、N=6 でも123810程度である。
また、実際にこの中で有効なのは、順序付きベル数と呼ばれるらしく、4683個しか無いらしい。
もっと上手な列挙の仕方はありそうな気がする。他の人のコードを読もう。
LISの求め方
大小関係の配列のLISをそのまま求めればよい。
パターン数の求め方
要は、D=(2,0,0,1) のような大小関係の列が固定されたとき、(15,3,3,8) のような具体的な数字の当てはめ方を数えたい。
0,1,2 に相当する数がそれぞれ上限いくつまでを使えるのかを整理する。A の対応する位置の最小値を見ればよい。
- Si=D 上で i に対応する数が、上限いくつまで使用可能か
A=(9,18,12,5) の場合、0 に対応する数は、対応する位置の Ai が 18,12 なので小さい方の 12 が上限となる。
例の場合、S=(12,5,9) となる。
各 i について 1≤Ti≤Si かつ狭義単調増加となるような、各整数の割り当て方 T を数える。
冗長なDP
Ai≤109 と大きいので、Ai の値をそのまま添字にしたDPは使えないが、まずはそれが可能として考える。
- DP[i][j]=S を i 番目まで考慮して、Ti=j である割り当て方のパターン数
すると、遷移は以下のようになる。
- DP[i][j]=j−1∑k=1DP[i−1][k](j≤Si の時)
- DP[i][j]=0(j>Si の時)
S (0) 1 2 3 4 5 6 7 8 9 10 11 12 始 1 0 0 0 0 0 0 0 0 0 0 0 0 12 - 1 1 1 1 1 1 1 1 1 1 1 1 5 - 0 1 2 3 4 0 0 0 0 0 0 0 9 - 0 0 1 3 6 10 10 10 10 0 0 0 → パターン数はこの総和 50 通り
毎回 Si を超えた範囲は0になるという特徴があるが、基本は累積和をとる更新となる。
累積和を開始する地点が、i ごとに1個ずつ後ろにずれていってしまうのが面倒くさい。
ここは「Ti≤Ti+1(広義単調増加)も許す」とすれば、常に累積和を1から開始できる。
S′i=Si−i として新たに S′=(12,4,7) とする。これがシフト後の Ti の上限となる。
→j S' (0) 1 2 3 4 5 6 7 8 9 10 11 12 始 1 0 0 0 0 0 0 0 0 0 0 0 0 12 - 1 1 1 1 1 1 1 1 1 1 1 1 4 - 1 2 3 4 0 0 0 0 0 0 0 0 ←1個左シフト 7 - 1 3 6 10 10 10 10 0 0 0 0 0 ←2個左シフト
ところで、このように繰り返し累積和が取られるような更新は、まとめて計算する方法がある。
j>S′i で0となってしまうのが不連続的だが、それが発生しない範囲では、i が進む毎に繰り返し累積和が取られるので、
→j i (0) 1 2 3 4 5 6 7 8 ... 0 1 0 0 0 0 0 0 0 0 ... 1 - 1 1 1 1 1 1 1 1 ... 2 - 1 2 3 4 5 6 7 8 ... 3 - 1 3 6 10 15 21 28 36 ... 4 - 1 4 10 20 35 56 84 120 ...
これは、斜めに見ると、二項係数が並ぶパスカルの三角形が現れる。
1 上の表は、横列を↘方向に見ている感じ 1 1 1 2 1 1 3 3 1 1 4 6 4 1
従って、0になる更新が発生しない範囲では、DP[i][j] の値は i+jCj、さらに DP[i][1]~DP[i][j] の総和は i+j+1Cj と、二項係数一発で求められる。
縮約したDP
さて、遷移がわかったところで、添字がでかすぎる問題に対処する。
S′ に出現する値で区切ることによって、各区間について、値を1個1個保持せずとも全体を上手く計算できないか考えてみる。この場合、区間数はたかだか6箇所となる。
(0, 4] (4, 7] (7, 12] 1 2 3 4 | 5 6 7 | 8 9 10 11 12
- DP′[i][s]=i 番目まで考慮して、s 番目の区間の○○(パターン数を上手く計算するために保持する何か)
(4,7] のように、「自分たちは0のまま、自分より下の区間で累積和が更新されており、いざ累積和を求める遷移が発生した際、なんか中途半端な値で開始される」というような区間が、どのように立式すればいいのか一見わかりづらい。
→j S' (0) 1 2 3 4 | 5 6 7 | 8 9 10 11 12 始 1 0 0 0 0 | 0 0 0 | 0 0 0 0 0 12 - 1 1 1 1 | 1 1 1 | 1 1 1 1 1 4 - 1 2 3 4 | 0 0 0 | 0 0 0 0 0 7 - 1 3 6 10 | 10 10 10 | 0 0 0 0 0 12 - 1 4 10 20 | 30 40 50 | 50 50 50 50 50 ~~~~~~~~~~ ↑どう全体をまとめて表現する?
DPの更新はすぐ左と上の足し算と言い換えられることを考えると、以下のように、前の区間の末尾の値が大きく関係してくることがわかる。
前区間末尾 | 区間 | 0 0 0 0 ... x | x x x x ... y | x+y 2x+y 3x+y 4x+y ... z | x+y+z 3x+2y+z 6x+3y+z 10x+4y+z ...
これを例えば x の係数についてのみ追いかけると、先ほどの二項係数の値がそのまま出てきている。
また、y,z も、x をそれぞれ追従する形で同じ係数が出てくる。
一度、区間全体が0になったら、x,y,z はそれ以降に伝播しないので、情報は捨ててしまってよい。
従って、連続して区間の累積和を取る更新がかけられ続ける限り、i 個前の前区間末尾の値 x は、
- 区間内の j 番目(0-index)の値に対し、i+jCjx だけ寄与する
- 区間長を L とすると、区間全体に x は L+i+1CLx だけ寄与する
「その区間に連続して累積和を取る更新がかけられ続けている間、0個前、1個前、…の前区間の末尾が何であったか」という情報さえ覚えておけば良いことがわかる。
実際は同じ計算を省略する目的などで、以下の3つの値をDPに持たせた。
- 「その区間内の合計、その区間末尾の値、現在有効な0個前,1個前,…の前区間の末尾の値リスト」
計算量は、順序付きベル数を FN として、
- 列挙が O(NN) 未満
- その中で有効なもの(FN 個)に対して
- LISは O(NlogN)
- パターン数の数え上げは O(N2)
全体で O(NN+FNN2) 未満となり、十分間に合う。
F - Visibility Sequence
問題
例
解法
1 |
|