区間DP。ループ回数は多め。
DP[l][r]=初期状態で左から [l,r) の区間のスライムを1匹に合成する最小コスト
全て0
例えば[2,7)の区間のスライムを1匹に合成するには、その直前で、[2,x) と [x,7) の2匹に分かれていないといけない。(xは3~6の任意の数)
逆に、[2,x) や [x,7) がどう合成されたものであろうが、それぞれのコストの最小値さえわかっていれば、[2,7) の合成コストは求められる。
このxを全て試し、最小を取るとよい。
DP[l][r]=minl+1≤x≤r−1(DP[l][x]+DP[x][r])+([l,r)のスライムの大きさ合計)
大きさの合計は、最初に累積和accを取っておけば、acc[r−1]−acc[l−1] で求められる。
1 2 3 4 5 6 7 8 9 10 |
from itertools import accumulate n = int ( input ()) acc = [ 0 ] + list (accumulate( map ( int , input ().split()))) dp = [[ 0 ] * (n + 1 ) for _ in range (n)] for w in range ( 2 , n + 1 ): for l in range (n - w + 1 ): r = l + w dp[l][r] = min (dp[l][m] + dp[m][r] for m in range (l + 1 , r)) + acc[r] - acc[l] print (dp[ 0 ][n]) |
bitDP。集合をビットフラグで表し、0から積み上げて 2N 通りの集合の値を計算する。
DP[i][s]=i 番目までの男たちが、集合 s で示される女たちとペアになるパターン数
DP[0][0]=1
i−1 番目の男まで計算済みとする。
i 番目の男と j 番目の女が好相性の時、
DP[i−1] の中で、まだ 女j が含まれていない集合 s に対して、DP[i][s|女j]+=DP[i−1][s] と遷移する。
たとえば j=5 とすると、下から5番目だけ立ったビットフラグb=10000を用意して、
…という処理を、DP[i−1] に含まれる各 s について行っていく。
i から i+1 への遷移で必要なのは、ビットフラグ s の N 本のビットのうち、立っているのがちょうど i 本のもののみ。 (男が i 人なので、女も同数のもののみ考慮すればよい) そのようなビットは、NCi 通りある。
「立っているのがちょうどk本のビットフラグのみのイテレート」はやりにくく、結局は 0~2N を回して条件に合う物のみ処理するのが単純だし早い。 ただ、この問題の場合「立っているのがちょうどi本のビットフラグ」は、i−1 から i への遷移の際に新しく作られた s に他ならない。 これを記録しておくことで、DP[i+1] の計算時に過不足のないイテレートが可能となる。 代わりに記録するコストがかかるので、言語によっては却って遅くなるかも知れないが……。
ともかく、NCi 通りのそれぞれで、男i と相性のよい女(最大 N人)の遷移を行うので、全体での計算量は
∑i=0..N−1N×NCi となる。これは、N=21 で約 4.4×107 となる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
n = int ( input ()) dp = [ 0 ] * ( 1 << n) dp[ 0 ] = 1 curr = { 0 } MOD = 10 * * 9 + 7 for i in range (n): compatibilities = [ 1 << i for i, v in enumerate ( input ().split()) if v = = '1' ] nxt = set () for k in curr: p = dp[k] % MOD for c in compatibilities: if k & c: continue dp[k | c] + = p nxt.add(k | c) curr = nxt print (dp[( 1 << n) - 1 ] % MOD) |
木DP。適当な頂点を根として、部分木の解を求めていく。
どの頂点から先に計算すれば良いかわかりづらいので、再帰関数で書くのがやりやすい。
一応、DFSで計算順序を求め、その順にDP配列を埋めていくやり方でボトムアップでも可能。PyPyは再帰が遅いので、PyPyの速度が必要な場合はそのようなやり方をする。
DP[i][w]=頂点1と根とした時、頂点 i 以下の部分木で、頂点 i 自身の色がw=白/黒だった場合のパターン数
DP[1] を再帰で求める
葉のパターン数: (白,黒) = (1,1)
頂点 i を黒にするには、子供が全部白でなくてはならない。逆に頂点 i が白なら、子供は白でも黒でもよい。
DP[i][白]=∏c∈iの子供(DP[c][白]+DP[c][黒])DP[i][黒]=∏c∈iの子供DP[c][白]
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 |
import sys sys.setrecursionlimit( 100005 ) MOD = 1000000007 def dfs(v, a): w, b = 1 , 1 for u in links[v]: if u = = a: continue cw, cb = dfs(u, v) w * = cw + cb b * = cw w % = MOD b % = MOD return w, b n = int ( input ()) links = [ set () for _ in range (n)] for line in sys.stdin.readlines(): x, y = map ( int , line.split()) x - = 1 y - = 1 links[x].add(y) links[y].add(x) print ( sum (dfs( 0 , None )) % MOD) |
セグメント木上でのDP
DP[i]=左から 1~i 本目までの花だけを考慮し、i 番目の花は必ず残す場合の、残る花の価値の最大値
セグメント木上で実装し、更新・取得をそれぞれ logN で行えるようにする
全て0
DP[i−1] まで確定して、i 番目の花を残すときを考える。
この時、「左から 1~i−1 番目に位置し、かつ hi より低い高さである花のうち、DP[j] が最大のもの」に、ai を加えたものが DP[i] となる。
↓いまここ 1 2 3 4 5 6 h 1 5 3 6 4 2 a 10 20 30 40 50 60 DP 10 30 40 70
i=5 より左にあり、h5=4 より低い花の中で、DP が最大のものは、DP[3]=40。よって、DP[5]=40+50=90 となる。(花1,3,5を残す時にこれが実現できる) 4より高い花が左にあると置けなくなってしまうので、自分より低い花の中から最大値を探すことになる。
位置と高さを両方考えるのはややこしいので、事前にhiでソートしておき、低い方から処理していくと、高さは無視できる。 花 i を処理時点での DP の値は必ずhi より低い花のみを考慮した値となっている。
↓いまここ 1 2 3 4 5 6 h 1 5 3 6 4 2 a 10 20 30 40 50 60 DP 10 40 70
セグメント木に対する更新は点更新、取得は必ず[1,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 |
def solve(n, hhh, aaa): n2 = 1 << n.bit_length() offset = n2 - 1 data = [ 0 ] * ((n2 << 1 ) - 1 ) def update(k, x): i = k + offset while i > = 0 : if data[i] < x: data[i] = x else : break i = (i - 1 ) / / 2 def get_max(k): i = k + offset ret = data[i] while i: # 自身が右枝の場合のみ、左枝と比較 if i % 2 = = 0 : ret = max (ret, data[i - 1 ]) i = (i - 1 ) / / 2 return ret srt = sorted ((h, i) for i, h in enumerate (hhh)) for h, i in srt: update(i, get_max(i) + aaa[i]) return data[ 0 ] n = int ( input ()) hhh = list ( map ( int , input ().split())) aaa = list ( map ( int , input ().split())) print (solve(n, hhh, aaa)) |
行列累乗
現在考慮中のパス長が k として、
DP[i][j]= 頂点iからj へ、パス長がちょうど k の行き方の通り数
全て0
有向グラフの経路パターン数は、辺の有無を0-1行列にし、累乗することで算出できる。
①-->②<->③ <-------> 1 2 3 1| 0 1 1 A^1 2| 0 0 1 3| 1 1 0 1| 1 1 1 A^2 2| 1 1 0 3| 0 1 2 1| 1 2 2 A^3 2| 0 1 2 3| 2 2 1
A31,2 を例に取ると、
(A)(B)(C) それぞれ同士を掛け合わせて合計すればよいが、この計算が行列の積算と一致する。
A31,2=A11,1A21,2+A11,2A22,2+A11,3A23,2=2
ただし、行列の積算は1回に N3 かかり、とても1018回は計算できない。 繰り返し二乗法を用いると、計算回数を log1018 に減らせる。
行列演算ならNumPyを使いたくなるが、オーバーフローするのかWAになる。以外と競プロでNumPy使う機会って無い。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
def dot(a, b): ret = [[ 0 ] * n for _ in range (n)] for i in range (n): for j in range (n): ret[i][j] = sum (a[i][k] * b[k][j] for k in range (n)) % MOD return ret n, k = map ( int , input ().split()) dp = [ list ( map ( int , input ().split())) for _ in range (n)] ans = [[ 0 ] * n for _ in range (n)] for i in range (n): ans[i][i] = 1 MOD = 1000000007 while k: k, i = divmod (k, 2 ) if i: ans = dot(ans, dp) dp = dot(dp, dp) print ( sum ( sum (a) % MOD for a in ans) % MOD) |