常見DP模型及其構造
序列DP
ARC074 RGB Sequence
題意
給你一個長度為 \(n\) 的序列和 \(m\) 組約束條件,每組條件形如 \(l_i,r_i,x_i\),表示序列上的 \([l_i,r_i]\) 中恰好有 \(x_i\) 種顏色,現在要你用三種顏色給這個序列染色,求滿足所有約束的方案數。
\(n,m \le 300\)。
技巧:設計出契合數據范圍的狀態
題解
注意到最多只有三種顏色,因此可以把顏色的信息記得暴力一些。設 \(dp[i][j][k]\) 表示三種顏色最后一次分別出現在第 \(i,j,k\) 個位置的的方案數。容易發現當前已經填過數的位置 \(i = \max\{i,j,k\}\),轉移十分顯然。
樹形DP
ARC086 E Smuggling Marbles
題意
給出一棵 \(n\) 個點的有根樹,初始時其中一些點上有一個石子,每次同時將所有石子從所在的點移動到父親上,根節點上的石子移動到籃子里。如果有一個點上的石子數大於 \(1\) 則移除所有石子,樹上沒有石子時結束。求所有 \(2^n\) 種初始局面經過操作后籃子里石子的期望數量。
\(n \le 2 \times 10 ^ 5\)。
技巧:長鏈剖分優化復雜度與向下深度相關的DP
題解
首先可以發現,每一層的DP是獨立的,因此可以分開考慮。可以先考慮一下 \(O(n^2)\) 的暴力DP。我們可以設 \(dp_{i,j,0/1}\) 表示 \(i\) 號結點的子樹,往下 \(j\) 層的點向上移動到 \(i\) 號節點,最后為 \(1/0\) 的方案數。直接暴力合並就可以做到 \(O(n^2)\) 的復雜度。
考慮如何優化。這種復雜度與節點向下的深度有關的DP,都可以用長鏈剖分進行優化。每次只要把長兒子的DP值直接從長兒子合並到父親節點。注意合並的時候不能直接賦值,否則復雜度會錯掉。再將其余兒子的信息合並上來。這樣復雜度是 \(O(n)\) 的。
考慮一下如何證明這個復雜度。為了方便,我們記不是長鏈的邊為輕邊。由於每個輕邊的下面一定是一條長鏈,所以每條輕邊合並的復雜度為它下面的長鏈的長度,而長鏈合並的復雜度也是長鏈的長度。因此總復雜度即為所有長鏈的總長度,也就是 \(O(n)\) 的。
數位DP
CF908G Original Order
題意
定義 \(f_n\) 表示將 \(n\) 的各個數位上的數排序后形成的數,例 \(f_{50394} = 3459\)。給定 \(n\),求 \(f_1 + f_2 + \cdots + f_n\)。
\(n \le 10^{700}\) 。
技巧:考慮每一位對答案的貢獻
題解
題目是個比較明顯的數位DP,但是直接做DP並不方便。考慮每個位的貢獻,如果從低位到高位的第 \(i\) 位至少是 \(j\) ,那么至少有 \(i\) 個數位大於等於 \(j\) 。而從低位到高位的第 \(i\) 位的貢獻,可以用第 \(i\) 位大於等於 \(1\) ~ \(9\) 的方案數相加得到。因此我們只要算出至少有 \(i\) 位大於等於 \(j\) 的方案數即可。
因此可以設計出數位DP,設 \(dp_{i,j,k,0/1}\) 表示前 \(i\) 位,有 \(j\) 位大於等於 \(k\) ,是否達到上界的方案數,轉移十分顯然。
清橙A1235 Digit
題意
求一個滿足條件的 \(n\) 位數 \(a\),滿足它的數字和為 \(s_1\),並且,\(a \times d\) 的數字和為\(s_2\)。注意 \(a\) 不能有前導 \(0\)。
\(1 \le n \le 100,0 \le s_1 \le 9n,0 \le s_2 \le 9(n + 1),0 \le d \le 9\)。
技巧:預處理DP處理貪心
題解
由於題目要求 \(a\) 是最小的,因此你需要類似按位貪心去填每個數。但是你又不好保證你填的數一定合法,因此需要一個預處理DP。設 \(dp[i][j][k]\) 表示 \(s_1\) 為 \(i\),\(s_2\) 為 \(j\),從這一位向前進位為 \(k\) 的最小長度。轉移的時候需要一個類似BFS的東西。
然后就可以按順序填每一位了。填數位的時候,枚舉一下下一位進位多少。填的時候注意一下 \(s_2\) 的進位會很鬼畜,所以直接暴力算進位后的數位和比較好。
LIS
題意
定義 \(f_n\) 表示將 \(n\) 的各個數位拆開形成序列的最長上升子序列,給定 \(l,r,k\) ,求滿足 \(l \le n \le r\) 且 \(f_n = k\) 的 \(n\) 的數量。
\(1 \le r \le r ≤ 10^{18},k \le 10\)。
技巧:DP套DP
題解
考慮我們是如何在 \(O(n\log n)\) 的時間內求LIS的。維護一個類似單調棧的東西,每次二分換掉大於當前元素的最小元素。於是這里也用類似的操作,利用狀壓DP與數位DP來完成這個過程。轉移相對比較顯然。
狀壓DP
AGC016 F Games on DAG
題意
給出一個 \(n\) 個點,\(m\) 條邊的 DAG ,每條邊的起點編號均小於終點編號。兩個人在圖上博弈,先在 \(1\) 號點和 \(2\) 號點上分別放一個棋子,每次操作可以將一個棋子沿一條邊移動。兩人輪流操作,直到不能操作的人輸。
求有多少邊的子集,滿足在只保留這個子集的圖上先手必勝。
\(1 \le n \le 15,1 \le m \le \frac{n(n - 1)}{2}\) 。
技巧:邊集相關的方案數轉化到點集上,再進行連邊算方案數。
題解
首先根據 SG 函數可以得到,原題可以等價於選擇一個邊集,使得 \(SG_1 = SG_2\) 。
我們設 \(f_S\) 表示考慮了 \(S\) 中的點之后,同時包含 \(1,2\) 節點且 \(SG_1 = SG_2\) ,或者不包含 \(1,2\) 號節點,但可以通過這個連邊方式使得 \(SG_1 = SG_2\) 的方案數。計算 \(f_S\) 的時候,可以考慮枚舉 \(S\) 中 \(SG\) 值為 \(0\) 的點集 \(x\) ,這里再記一個 \(y\) 為 \(x\) 在全集 \(S\) 下的補集。這里強調一點,點集 \(x\) 中的點是在加入了 \(y\) 集合中的點之后,所有點的 \(SG\) 值變為 \(0\) 的。
考慮 \(x\) 集合與 \(y\) 集合之間的連邊需要滿足哪些條件:
- \(x\) 集合內部沒有連邊;
- \(x\) 向 \(y\) 集合的連邊任意;
- \(y\) 集合中的每個點至少與 \(x\) 集合中連一條邊;
- \(y\) 集合內部的連邊方案恰好為 \(f_y\);
其中第 \(4\) 條相對難以理解一些。首先可以注意到一點,對於一個不包含 \(1\) 號點與 \(2\) 號點的點集,一定有辦法通過加邊使得 \(SG_1 = SG_2\) ,因為你可以直接把 \(1,2\) 節點定為沒有出邊的點,這樣 \(SG_1 = SG_2 = 0\) 。於是第 \(4\) 條對於 \(1,2\) 號節點不在 \(S\) 中的情況就一定成立了。對於 \(1,2\) 號節點同時位於 \(x\) 集合,那么 \(SG_1 = SG_2 = 0\) ,這一結論也顯然成立。對於 \(1,2\) 號節點同時位於 \(y\) 集合,只要將所有 \(y\) 集合中的點連向至少一個 \(SG\) 值為 \(0\) 的點,這樣 \(y\) 集合中的點的 \(SG\) 值會同時增加 \(1\) ,\(y\) 集合內部的連邊方案數恰好為 \(f_y\) 。
由於 \(1\) 號結點與 \(2\) 號結點之間不能有連邊,因此 \(1,2\) 號節點不能同時位於不同的集合中。於是我們只需要一個子集枚舉的狀壓DP即可。
DP優化
基本優化
一些基本常用的套路優化:
設計狀態和轉移的時候盡量剔除不必要的部分。
冷靜分析復雜度,特別是有關樹上DP的題目。
狀壓DP中的常數優化往往有意想不到的效果。
對於狀態直接存儲存不下的題目,利用
std::map
或者哈希表。
特別注意樹形DP的時候,自己經常沒注意一個地方然后算錯復雜度。就是樹形DP的時候,如果每個節點狀態的大小為每個子樹的大小,合並時必須枚舉每個子樹的每個狀態進行合並,那么復雜度並不是 \(O(n^3)\) 而是 \(O(n^2)\) 的。原因是任意兩個點只會在LCA處貢獻 \(1\) 的復雜度。
斜率優化
這個玩意兒我是真的學一次忘一次,這一次學徹底一點,加強一下理解,免得下次又忘了。
斜率優化用於最優化題目中快速找到最優解。一般來說,可以通過推式子分析題目最優轉移的凸殼形式。維護凸殼一般要分析凸殼上點的加入順序選擇合適的數據結構。對於插入的點橫坐標單調,查詢的直線的斜率也單調,就可以用單調隊列優化。對於查詢的直線斜率不單調,需要在凸包上二分;對於插入的點橫坐標不單調,則需要動態凸包或者離線CDQ分治。
這段話里頭提到了兩個概念,可能有些不太清晰。一個就是查詢直線的斜率,另一個是插入的點的坐標。這兩個東西如果數形結合一下,會有更好的理解。考慮斜率優化的一個經典的式子。
假設 \(b\) 單調考慮兩個決策點 \(j,k\) ,不妨設 \(k < j\) ,那么如果決策點 \(k\) 優於決策點 \(j\) ,就會有
於是通過這個式子,我們可以比較好地理解上面所說的兩個概念了。在這個式子里頭,\((b_k,-d_k)\) 就是 \(k\) 決策點對應的坐標,\((b_j,-d_j)\) 就是 \(j\) 號決策點對應的坐標;而 \(a_i\) 就是 \(i\) 號點查詢時直線的斜率。
通過這樣的數形結合,我們也能很好地理解單調隊列解決斜率優化的幾個步驟了。由於插入的點的橫坐標單調,因此我們可以直接用隊列來維護決策凸包。而有因為查詢的斜率單調,因此我們要及時彈出隊首那些隨着查詢直線斜率單調變化而不再可能成為最優解的點。還因為對於每一個決策點,我們已經證明了在當前詢問直線斜率下,橫坐標靠前的決策點一定比決策點靠后的點更優,因此隊首就是當前決策的答案。
UOJ104 APIO2014 Split the sequence
題意
你正在玩一個關於長度為 \(n\) 的非負整數序列的游戲。這個游戲中你需要把序列分成 \(k+1\) 個非空的塊。為了得到 \(k+1\) 塊,你需要重復下面的操作 \(k\) 次:
- 選擇一個有超過一個元素的塊(初始時你只有一塊,即整個序列)
- 選擇兩個相鄰元素把這個塊從中間分開,得到兩個非空的塊。
每次操作后你將獲得那兩個新產生的塊的元素和的乘積的分數。你想要最大化最后的總得分。
\(n \le 100000,k \le 200\)。
技巧:斜率優化DP
題解
考慮操作順序對答案的影響。容易發現,每次分割就讓你獲得了這兩個塊之間的兩兩元素之間的貢獻。於是最后你沒有拿到的貢獻也就是在同一個塊內的元素兩兩之間的貢獻。這也就意味着你的選擇順序不會影響你最后的總得分。
於是我們就可以直接套用斜率優化DP來解決這個問題。注意到這里無論是查詢直線的斜率還是插入點的橫坐標都是單調的,因此可以直接用單調隊列優化。
凸優化
個人認為課件里對於凸優化的解釋相當的清楚所以我就偷個懶了……
凸優化解決的是一類選擇恰好 \(k\) 個某種物品的最優化問題。一般來說這樣的題目在不考慮物品數量限制的條件下會有一個圖像,表示選擇的物品數量與問題最優解之間的關系。
問題能夠用凸優化解決還需要滿足圖像是凸的,直觀地理解就是選的物品越多的情況下多選一個物品,最優解的增長速度會變慢。
解決凸優化類型的題目可以采用二分的方法,即二分凸殼上最優值所在點的斜率,然后忽略恰好 \(k\) 個的限制做一次原問題。
這樣每次選擇一個物品的時候要多付出斜率大小的代價,就能夠根據最優情況下選擇的物品數量來判斷二分的斜率與實際最優值的斜率的大小關系。
理論上這個斜率一定是整數,由於題目性質可能會出現二分不出這個數的情況,這時就需要一些實現上的技巧保證能夠找到這個最優解。
凸優化是DP優化的一種重要思想,感覺這些題目都還挺巧妙的,積累一些套路。因為這是一個我沒接觸過的套路,所以還是放了點水題。
CF739E Gosha is hunting
題意
現在一共有 \(n\) 只怪物。你有 \(a\) 個A型球和 \(b\) 個B型球。A型球抓到第 \(i\) 只神奇寶貝的概率是 \(p_i\) ,B型球抓到的概率則是 \(u_i\)。不能往同一只怪物上使用超過一個同種的球,但是可以往同一只上使用兩種球(都抓到算一個)。你要制定一種方案,最大化抓到的怪物數的期望最大值。
\(n,a,b \le 2000\)。
技巧:凸優化簡單應用
題解
首先可以得到一個 \(O(n^3)\) 的DP。設 \(dp_{i,j,k}\) 表示前 \(i\) 只怪物,用了 \(j\) 個A型球和 \(k\) 個B型球抓到的怪物數最大的期望值,轉移比較顯然。容易發現,隨着A型球與B型球的數量增多,DP的增長速度會越來越慢,因此可以考慮凸優化。凸優化大概就是二分一次A型球的收益。然后對於每次操作的收益都減去這個二分的收益,做一次沒有次數限制的DP。如果DP出來使用的A型球的個數大於 \(a\) ,那么就要加大這個二分的斜率,否則要放緩這個二分的斜率。對於兩維都可以這樣凸優化,因此復雜度為 \(O(n \log^2 L)\)。
LOJ2478 九省聯考2018 林克卡特樹
題意
現在有一個 \(n\) 個點的樹,每條邊有一個整數邊權 \(v_i\),若 \(v_i \ge 0\),表示走這條邊會獲得 \(v_i\) 的收益;若 \(v_i < 0\),則表示走這條邊需要支付 \(-v_i\) 的過路費。小L需要控制主角Link切掉樹上的恰好 \(k\) 條邊,然后再連接 \(k\) 條邊權為 \(0\) 的邊,得到一棵新的樹。接着,他會選擇樹上的兩個點 \(p,q\),並沿着樹上連接這兩點的簡單路徑從 \(p\) 走到 \(q\),並為經過的每條邊支付過路費/ 獲取相應收益。
求Link能得到的總收益-總過路費最大是多少。
\(0 \le k < n \le 3 \times 10^5\)。
技巧:條件信息的轉化
題解
題目經過轉化之后,可以發現,最后的所求可以看成,你需要從一棵樹中選擇 \(k + 1\) 條不相交的鏈。最大化這 \(k\) 條鏈的邊權和。
可以想到一個樹形DP。設 \(dp[i][j][0/1/2]\) 表示 \(i\) 所在的子樹,選擇了 \(j\) 條鏈。\(0/1/2\) 分別表示 \(i\) 號點與兒子沒有連邊,連了 \(1\) 條邊與 \(2\) 條邊的最大邊權和,轉移要稍微討論一下。直接DP可以做到 \(O(nk)\) 的復雜度,需要優化。
可以發現,隨着加入的邊越來越多,增加的邊權一定會越來越小,因此可以直接套用凸優化,復雜度優化到 \(O(n\log W)\)。
這個題實現上注意妙用重載運算符。像我這種寫一大堆 if
的代碼,是真的調不出……只能抄抄網上的代碼維持生活這個樣子。
LOJ566 YQL 的生成樹
題意
對於可重實數集 \(\{a_i\}\),定義離差為:對於任意實數 \(d\),\(\sum_i |a_i − d|\) 的最小值。
現在給出一個 \(n\) 個點,\(m\) 條邊的無向聯通圖,求最大離差生成樹。
\(n \le 2 \times 10^5,m \le 5 \times 10^5\)。
技巧:尋找巧妙性質
題解
首先不難發現,這里的 \(d\) 應當是 \(a_i\) 的中位數,於是我們可以考慮枚舉中位數。我們將所有邊權大於中位數的邊染黑,其余邊染白,那么我們就只要求恰好包含 \(\lfloor\frac{n - 1}{2}\rfloor\) 的最大生成樹即可。這就是一個凸優化的經典問題了。直接二分一個權值,對於所有的黑邊都加上這樣一個權值。然后根據所選黑邊的條數來調整這個加上的權值即可。直接這么做,復雜度是 \(O(nm\log W)\) 的,不足以通過此題。考慮如何繼續優化。
容易發現瓶頸在於中位數的枚舉。我們可不可以不枚舉這個中位數呢?
考慮我們現在的做法,先枚舉中位數 \(M\),然后二分增長率 \(k\) 。令原來邊權為 \(w\) 的白邊邊權為 \(M - w + k\),原來邊權 \(w\) 的黑邊邊權為 \(w - M\)。注意到對於所有邊權同時加上一個 \(M\) 對答案沒有影響,因此兩種邊的邊權分別轉為 \(2M - w + k\) 與 \(w\)。
這里我們會有一種想法,如果我們令 \(C = 2M + k\) ,則黑白邊邊權分別為 \(C - w\) 和 \(w\) 。如果我們考慮轉而二分 \(C\),由於這里我們確定的順序有所改變,我們先確定了中位數,再來考慮每條邊的顏色,因此初步分析可能會有最終黑邊的邊權 \(< M\) 的情況從而導致答案錯誤。也就是說,我們只有保證了對於最終所選出來的黑邊與白邊,黑邊的邊權都 \(\ge M\) 且白邊的邊權都 \(< M\) ,這個做法才正確。
首先黑邊的邊權是一定大於等於白邊的邊權。因為我們選出來的是最大生成樹。對於一條邊權為 \(w_1\) 的黑邊和一條邊權為 \(w_2\) 的白邊,並存在 \(w_1 \le w_2\) 的關系。如果我們交換這兩條邊的顏色,那么答案一定不會更劣;
然后還需要排除掉兩種情況,一種是黑邊中邊權最小的邊的邊權小於 \(M\) ,另一種是白邊中邊權最大的邊邊權大於 \(M\) 。我們不妨考慮第一種情況。如果存在一條邊權為 \(w\) 黑邊滿足 \(w < M\) ,我們直接令 \(M = w\) ,容易發現這樣所有權值 \(< M\) 的黑邊,貢獻都會變大,也能轉化為更優的方案。於是這種做法也就得證了。
求最小生成樹的時候可以預處理排序。整個復雜度為 \(O(m\log n\alpha(n))\) 。
分治與數據結構優化
數據結構優化應該司空見慣了,這里就不在贅述了。重點講講分治優化。
這里的分治優化並不是指分治優化某個值的計算,而是利用分治簡化某些棘手的限制。
BZOJ4380 Myjnie
題意
\(n\) 家洗車店從左往右排成一排。有 \(m\) 個人要來消費,第 \(i\) 個人會駛過 \([a_i,b_i]\) 之間的洗車店,並選擇這些店中最便宜的一個進行一次消費,但是如果這個價格大於 \(c_i\),這個人就不會消費。
請給每家店指定一個價格,使得所有人花的錢的總和最大。
\(n \le 50,m \le 1000,1 \le a_i \le b_i \le n, c_i \le 5 \times 10^5\)。
技巧:利用分治思想簡化限制條件
題解
容易發現這是一個區間DP的模型。對 \(c_i\) 離散化之后,設 \(dp_{i,j,k}\) 表示從 \(i\) 到 \(j\) 的區間內,只考慮消費區間位於 \([l,r]\) 的顧客的貢獻,洗車店價格最小值為 \(k\) 的最大收益。那么我們區間合並的時候,先枚舉這個區間的最小值 \(l\) 在什么地方,然后考慮合並 \([i,l - 1]\) 和 \([l + 1,j]\) 兩個區間。這里需要預處理出消費區間穿過 \(l\) 這個點的每個顧客中,\(c_i\)大於等於 \(k\) 的顧客數。然后就可以直接轉移了。
題目還要求輸出方案。記方案的時候,除了要記每個區間的最小值的位置,還要記每個區間每個位置對應的價格。
決策單調性與四邊形不等式優化
本質上這兩個東西是一個東西。四邊形不等式優化只是決策單調性優化的二維版本。一維決策單調性優化常用的方式是分治或者單調棧。
感覺自己對這一塊非常陌生,通過一道題來簡單了解一下,熟悉一下套路。
LOJ6039 珠寶
題意
有 \(n\) 個珠寶,每個珠寶價值 \(c_i\),能產生 \(v_i\) 的愉悅度,現在你有 \(m\) 元,問你最多能獲得多大的愉悅度,對於 \(m \in [1, k]\) 回答問題。
\(n \le 10^5,c_i \le 300,v_i \le 10^9,k \le 5 \times 10^4\) 。
技巧:決策單調性簡單題
題解
首先注意到 \(c_i\) 的范圍很小,因此可以把珠寶按照 \(c_i\) 分層。對於 \(c_i\) 相同的珠寶,顯然我們會取愉悅度前若干大的珠寶,\(c_i\) 相同的我們在同一層轉移。直接這么做顯然復雜度爆炸,因此還需要優化。
注意到每個 \(c_i\) 相同的珠寶,按照 \(v_i\) 從大到小排序,前綴和的增長率是單調遞減的,而且對於 \(c_i\) 相同的層,如果我們將決策點按照 \(\bmod c_i\) 的余數對決策點分類,\(dp\) 值也是單調的。這樣我們可以得到,這個 \(dp\) 在同一類的決策也是單調的,為什么呢?
設 \(x\) 號點的決策點為 \(j\),而 \(y\) 號點的決策點為 \(k\) ,且 \(y > x\) 。假設 \(k < j\),那么就會有:
其中 \(Sum[x]\) 表示使用 \(x\) 元購買價值為 \(c_i\) 的珠寶,能夠產生的最大愉悅度。由於 \(Sum\) 增長率是單調的,容易發現 \(Sum[x - k] - Sum[x - j] > Sum[y - k] - Sum[y - j]\) ,與上式矛盾。於是我們便得到了決策是單調的。
得到這一點之后,可以對於每一層每一類的轉移,利用分治進行優化。
首先一個很蠢但是我想了很久的問題,為什么需要分治呢?因為你對於每個點還是要從上一個決策點掃一遍才能得到最大值。決策點雖然單調,但沒有快速求的辦法。
分治是怎么做的呢?首先分治下去的時候,傳下去四個參數 \(L,R,ql,qr\) 。\(L,R\) 表示考慮 \(ql\) 到 \(qr\) 這個區間內的轉移的決策點在 \(L\) 到 \(R\) 內。然后你需要找到 \(mid = \frac{l+r}{2}\) 的轉移的決策點。這個可以掃一遍 \(L\) 到 \(R\) 得到。然后就可以遞歸子區間解決。
於是時間復雜度 \(O(ck \log k)\) 。