早有打算做點\(dp\)題,今天恰好看到gyh寫了一個DP從入土到入門,那就硬抄唄(狗頭
線性DP
顧名思義,就是在線性狀態上進行遞推
常見在序列上對區間操作的問題,如區間合並
P4910 帕秋莉的手環
給定一個有\(n\)個位置的環,每個位置可以填入\(1/0\),要求不能有相鄰的\(1\)
多組數據\(T\leq 10,n\leq 10^{18}\)
- \(20pts\)
暴力枚舉
- \(60pts\)
\(f[i][0/1]\)表示第\(i\)個位置是\(0/1\)的方案數
\(\begin{array}{l} \left\{\begin{matrix} f[i][1]=f[i-1][0]\\f[i][0]=f[i-1][1]+f[i-1][0]\end{matrix}\right.\end{array}\)
復雜度為\(O(Tn)\),期望得分\(60pts\)
- \(100pts\)
考慮矩陣優化
\(\begin{bmatrix}f_{i-1,0}&f_{i-1,1}\end{bmatrix} \times \begin{bmatrix}1&1\\1&0\end{bmatrix} = \begin{bmatrix}f_{i,0}&f_{i,1}\end{bmatrix}\)
復雜度為\(O(Tlogn)\)期望得分\(100pts\)
代碼見P4910 帕秋莉的手環
P4933 大師
給定\(n\)個建築,第\(i\)高度為\(h[i]\),求拆掉某些建築后剩下的是等差數列的方案數(對\(998244353\)取模)
\(n\le 1000,h_{max}\le 20000\)
- \(30pts\)
枚舉每個建築拆不拆
復雜度為\(O(2^{n}*n)\),期望得分\(30\)
- \(60pts\)
枚舉等差數列的公差,然后每次掃一下整個序列,看看能否滿足,\(h_{max}\le2000\)
上面的是錯誤的算法,我傻了
但是我們注意到值域\(h_{max}\le 2000\),其實是很小的
大膽猜測枚舉公差的方向肯定沒錯,淦
用\(f[i][j]\)表示以\(i\)結尾,公差為\(j\)的等差數列有多少個
每次枚舉一個小於\(i\)的位置\(k\),讓\(k\)的高度滿足\(h[k]=h[i]-j\),然后用\(f[k][j]\)更新\(f[i][j]\)
復雜度為\(O(n^2*h_{max})\),期望得分\(60\)
- 真\(100pts\)
考慮優化\(60pts\)
等差數列任意相鄰兩個數的公差是一定的
我們沒必要去枚舉公差,直接后面的減去前的就得出來公差了,\(qwq\)
\(f[i][h_i-h_k]=\sum\limits_{k<i}f[k][h[i]-h[k]]+1\)
復雜度為\(O(n^2)\),期望得分\(100\)
代碼見P4933 大師
P3847 調整隊形
給定長為\(n\)的顏色序列,顏色為\(1\)到\(n\),問最少經過多少次操作能讓顏色序列左右對稱
操作有添加,刪除,插入,替換顏色
\(n \le 3000\)
仔細分析題目題解可以發現:
- 在隊伍中任兩個人中間插入一個人(衣服顏色依要求而定)
- 剔掉一個人
這兩個操作是沒必要的,因為插入一個人的狀態一定包含在加入一個人的狀態里面,同理剔除一人和加入一個人等效
所有我們就只需要處理加入一個人和換顏色即可
\(f[i][j]\)表示\(i\)到\(j\)對稱需要的最小操作次數
- 加入一個人
\(f[i][j]=\min(\min(f[i][j-1]+1,f[i+1][j]+1),f[i][j])\)
- 換顏色
\(f[i][j]=\min(f[i+1][j-1],f[i][j])\)
代碼見P3847 調整隊形
P4170 塗色
給定一個長為\(n\)的序列,每次可以把一段塗成某種顏色,后塗的會覆蓋先塗的,問最少塗幾次能得到目標狀態
\(n\le50\)
和上面拿到題目類似
設\(f[i][j]\)表示\(i\)到\(j\)最少的塗色數量
分情況討論
\(i=j\)時,顯然有\(f[i][j]=1\)
\(i\neq j\)時
- 如果\(a[i] = a[j]\),就繼承一下上一次塗的色,\(f[i][j]=\min(f[i+1][j],f[i][j-1])\)
- 如果\(a[i]\neq a[j]\),就把顏色看成兩段,枚舉分割點\(k\),\(f[i][j]=\min(f[i][k]+f[k+1][j])\)
代碼見P4170 塗色
P3146 248 G
給定一個\(1*n\)的地圖,在里面玩\(2048\),每次可以讓相鄰的兩個相同的數\(x\)合並成\(x+1\),問最大能合出多少。
\(1\leq x \leq40\)
這種合並操作基本就是區間\(DP\)(狗頭
設\(f[i][j]\)表示\(i\)到\(j\)合並的最大值(區間\(DP\)經典狀態)
則顯然轉移方程就是\(f[i][j]=\max(f[i][j],f[i][k]+1),k\in[l,r),f[i][k]=f[k+1][j]\)
這樣就可以水過本題了,當時其實這樣是錯誤的
\(Hack\)數據:
Input:
8 2 1 1 2 4 2 3 4
Output:
4
如果\(f[i][k]=f[k+1][j]=0\)的話,按照上述轉移方程得到的\(f[i][j]=1\),就錯了
\(f[i][j]=\max(f[i][j],f[i][k]+1),k\in[l,r),f[i][k]=f[k+1][j]>0\)
代碼見P3146 248 G
P5774 病毒感染
有\(n\)個小鎮,每個小鎮\(a_i\)個人感染了病毒,\(JYY\)每到一個村庄可以選擇救治該村所有的人(這一天沒有人死亡),或者去下一個村庄,當他已經跳過一個村庄后再向該村庄走時(此處不明白請仔細看題目中絕對值的關系那個地方),必須來救治該村庄
第\(i\)個村假設有\(a_i\)個人感染,那么第二天這\(a_i\)個人會死亡且會新感染\(a_i\)個人,求最小死亡數
\(1\le n\le3000,1\le a_i\le10^9\)
這題真的是普及+/提高嗎?
- \(50pts\)
定義\(f[i]\)為治愈前\(i\)個村庄的最小死亡數
對於每個村庄來說\(JYY\)可以選擇治療還是不治療,重點在於怎么處理回去這的問題
由題目中\(|k-i|<|k-j|\)可得只要想從\(j\)回去治療\(k\),那就必須把之前的全都治了
所以我們只需要枚舉\(k\)即可實現轉移
復雜度\(O(n^3)\),期望得分:\(50\)
- \(100pts\)
上面的做法時間主要浪費在計算略過村庄再回來的死亡人數上,我們可以考慮預處理這一部分
定義\(g[i][j]\)表示從\(i\)到\(j\)在回到\(i\)的最少死亡人數
但是我不知道\(g[i][j]\)應該怎么轉移(看不懂題解),先挖個坑,以后回來寫完
回來填坑了
枚舉\(i\)為起點,\(j\)為長度,\(sum[i]=\sum\limits_{k=1}^{i}a[k]\)
\(g[j][i+j]=g[j+1][i+j]+\min((sum[i+j]-sum[j])*2,a[j]*i*3+sum[i+j]-sum[j])\)
則設\(f[i]\)為前\(i\)個村庄消滅疫情最小死亡人數
\(f[i]=\min(f[j]+g[j+1][i]+(4*i-4*j-2)*(sum[n]-sum[i]))\)
淦,這轉移真惡心,如果看不懂請點這里
代碼見P5774 病毒感染
P2501 [HAOI2006]數字序列
給定一個\(n\)個數的序列,問最少改變多少個數能讓它變成一個單調嚴格上升的序列,且讓每個數改變的絕對值之和最小,輸出個數和最小值
\(1\le n\le3.5*10^4,1\le a_i\le 10^5\)
- \(Problem\ 1\)
看懂題目之后,淦,這什么東西啊
我陷入了滿足的數應該被改變的思考中......嗯,想不出來
正着想不出來可以反着想,考慮滿足什么條件的數應該被保留
如果\(a[i]\)和\(a[j]\)之間的數本身合法或者能被改成合法的那么它們就是應該被保留的
容易得出滿足\(a[j]-a[i]\ge j-i\)
即\(a[j]-j\ge a[i]-i\)
顯然,可以令\(b[i]=a[i]-i\),然后求\(b\)的最大不下降子序列(因為條件是\(\ge\))的長度\(len\),\(n-len\)即為第一問答案
- \(Problem\ 2\)
我們繼續在\(b\)上考慮,對於一對被保留的點\(b[i]\)和\(b[j]\),他們之間的數一定都是不合法
現在的問題就在於如何把這些數都改變才能使改變量最小
一個結論
設分界點為\(k\),則對於左邊\(b[l] \to b[i],l\in[i,k]\)對於右邊\(b[r]\to b[j],r\in [k+1,j]\)
證明的話就不寫了,我也不會
設\(g[i]\)表示\([1,i]\)的在改變次數最小時最小改變量之和
\(g[i]=\min(g[j]+w(j+1,i))\)
根據上面的結論可以通過前綴和和后綴和來優化求\(w(j+1,i)\)的過程
復雜度為\(O(n^2+nlogn)=O(n^2)\),期望得分:\(100\)
P5301 [GXOI/GZOI2019]寶牌一大堆
不簡化了,太長了
做的第一道NOI/NOI+/CTSC的\(DP\),做這個題要時刻記得各種牌是怎么組成的,\(qwq\)
雀魂大毒瘤,麻將已無愛
- 七對子
直接貪心選擇權值最高的七個即可
- 國士無雙
暴力枚舉,\(O(13^2)\)
- \(3*4+2\)
設\(f[i][j][k][a][b][c]\)表示枚舉到第\(i\)張牌,已經有了\(j\)張面子,有有\(k\)對雀頭也可,第\(i\)種牌用了\(a\)張,第\(i+1\)種用了\(b\)張,第\(i+3\)種用了\(c\)張的最大分數,或者\(k=0/1\)
然后枚舉轉移即可
- 性質\(1\)
經過艱難的讀題后可以發現杠子是不需要考慮的,因為杠子的價值\(C_4^{4}=1\)就算是寶牌也沒有刻子的高\(C_4^{3}=4\)
- 性質\(2\)
三個刻順子是沒有三個刻子更優的,所有對於\(l,m\)只需要枚舉到\(2\)就行了
- 性質\(3\)
\(f\)值是\(0\)的話說明不合法直接退出即可
- 轉移方程
\(f[i][j][1][k+2][l][m]=max(f[i][j][1][k+2][l][m],\frac{f[i][j][0][k][l][m]}{C_{a[i]}^k}*C_{a[i]}^{k+2}*dora[i]^2)\)
\(f[i][j+1][o][k+3][l][m]=max(f[i][j+1][o][k+3][l][m],\frac{f[i][j][o][k][l][m]}{C_{a[i]}^k}*C_{a[i]}^{k+3}*dora[i]^3)\)
\(f[i][j+1][o][k+1][l+1][m+1]=\max(f[i][j+1][o][k+1][l+1][m+1],\frac{f[i][j][o][k][l][m]}{c_{a[i]}^k}*c_{a[i]}^{k+1}*\frac{dora[i]}{c_{a[i+1]}^l}*c_{a[i+1]}^{l+1}*\frac{dora[i+1]}{c_{a[i+2]}^m}*c_{a[i+2]}^{m+1}*dora[i+2])\)
- 注意
注意在轉移之前判斷合法不合法(剩的牌夠不夠),開long long
為什么要開\(O2\)才能過?我吧我忘記判斷不合法狀態了,\(qwq\)
復雜度為\(O(34*4*4*2*2)=O(2176)\),期望得分:\(100\)
51Nod 1327 棋盤游戲
思路題
給定\(n\cdot m\)的方陣,每個格子可以填\(1\)或者\(0\),求每一列最多只有\(1\)個\(1\),第\(i\)行從左開始向右連續\(left[i]\)個格子只有\(1\)個\(1\),從右邊開始向左連續\(right[i]\)個格子只有\(1\)個\(1\)的方案數
讀完題感覺有點像\(CSPday2\)的題,怪
有一種濃濃的區間\(dp\)的味道,這道題的顯然不能按照行來做(行的限制比較多,很難搞),所以我們應該按照列來定義狀態\(f[i][j][k][0/1]\)為到第\(i\)列,左邊的列填了\(i\)個\(1\),右邊的列填了\(j\)個\(1\),當前列有沒有\(1\),這樣轉移不了啊
我們可以思考每一列可以放的情況:如果一些行的左區間的終點在該列,那么這些行是必然要被放的(不然就不符合要求了),如果一些行的右區間從這一列開始,那么沒有處理完的行就會多出幾個,放到后面的狀態處理即可,還有一種情況就是選擇\(left\)和\(right\)之間的部分放一個\(1\)
如下圖(ps:圖畫的不太對,藍線應該在格子里,自己\(YY\)吧,我懶得改了)所示,在藍色線那一列,第\(1\),\(2\),\(4\),\(6\)的紅色必定已經填過了,而且黃色的第\(1,2\)行可以填了,第\(4,6\)行就是可以選擇\(left\)和\(right\)之間的部分放一個\(1\)的情況
那么狀態就可以定義了\(f[i][j][k]\)表示到了第\(i\)列,在這之前有\(j\)列沒有放過妻子,且已經有\(k\)行到了有區間(黃色部分)
記\(l_i,r_i,mid_i\)分別表示第\(i\)行左區間右端點為\(i\),右區間左端點為\(r_i\),第\(i\)列沒有覆蓋住的行數
根據上面的分析:每一列的轉移要添加\(r_{i+1}\)的右區間放置機會,要將\(l[i+1]\)全部搞定。
則轉移為:
- 放在紅色格子
\(f[i+1]\left[j+1-l_{i+1}\right]\left[k+r_{i+1}\right]+=f[i][j][k] * A_{j+1}^{l_{i}+1}\)
- 放在黃色格子
\(f[i+1]\left[j-l_{i+1}\right]\left[k+r_{i+1}-1\right]+=f[i][j][k] * A_{j}^{l_{j+1}} *\left(k+r_{i+1}\right)\)
- 放在白格子
\(f[i+1]\left[j-l_{i+1}\right]\left[k+r_{i+1}\right]+=f[i][j][k] * A_{j}^{l_{i+1}} * m i d_{i+1}\)
狀壓 DP
顧名思義,就是通過二進制壓縮,將狀態轉化為一個數,用這個數作為動態規划的狀態進行 \(DP\)
常見於某些數據范圍較小的題目,如\(n\le32\),\(n\le 64\)等
P3622 [APIO2007]動物園
給定一個有\(n\)個動物的環,你可以移除某些動物,有\(c\)個小朋友,每個小朋友能看到\(5\)個動物,每個小朋友有喜歡的和不喜歡的動物
定義小朋友高興為
- 至少看到他喜歡的一個動物
- 至少有一個他不喜歡的動物被移除
問最多有多少小朋友高興
\(10\le n \le10^4,1\le c\le5*10^5\)
每個人只能看到\(5\)個動物而且每種動物只有\(0/1\)兩種狀態,這提示我們使用狀壓來做
不妨先考慮環的問題,顯然可以每\(5\)個划分狀態
設\(f[i][s]\)表示從到\(i\)且\([i,i+5)\)這幾個動物的狀態為\(s\)時最多能使多少小朋友開心
\(f[i][s]=\max(f[i-1][(s\&15)<<1],f[i-1][(s\&15)<<1|1])+sum[i][s]\)
\(sum[i][s]\)表示狀態是\(s\),視野為\([i,i+5)\)的小朋友高興的人數
- 對環的處理
環上第\(n\)個動物時的狀態\(s\)的后四位必須與第\(1\)個動物的前四位的狀態相同
則只需強制開始狀態和結束狀態一樣才能更新,可以通過外層在加一個循環來表示初始狀態來實現,具體見代碼
復雜度為\(O(32^2\times n)\),期望得分:\(100\)
P2150 [NOI2015]壽司晚宴
給定\(n-1\)中不同的壽司,第\(i\)種壽司的美味度為\(i+1\),小\(G\)和小\(W\)從中挑選一些來品嘗,要求他們選得壽司中美味度必須都互質,問有多少種方案(對\(p\)取模)
\(2\le n \le500,0<p\le10^9\)
- \(30pts\)
\(n\le 30\)一共有\(10\)個質數,我們可以狀壓一下兩人已經選的集合
顯然對於一個壽司,如果它的質因子都不在在小\(G\)的集合里,那么它可以放入小\(W\)的集合里面
\(f[i][s1][s2]\)表示到第\(i\)個壽司,小\(G\)選擇的質因子集合是\(s1\),小\(W\)選的是\(s2\)
$\left{\begin{matrix} f[i][s1|p_i][s2]+=f[i-1][s1][s2] & p_i&s2=0 \ f[i][s1][s2|p_i]+=f[i-1][s1][s2] & p_i&s1=0 \end{matrix}\right. $
\(p_i\)為第\(i\)個壽司的質因數集合
上述轉移方程竟然可以通過滾動數組來優化(奇怪的知識增加了
復雜度\(O(2^{10}\times n)\),期望得分\(30\)
- \(40pts\)
沒想到有什么只能過\(40pts\)的,如果有只能過\(40pts\)的麻煩告訴蒟蒻一下
- \(70pts\)
同樣不知道,\(qwq\)
- \(100pts\)
對於\(1\)個數\(n\)它至多有\(1\)個\(>\sqrt{n}\)的質因子
根據上面的性質,我們沒必要把所有質因子都狀壓,只需要將質因子進行分類
一類是”小因子“\(<\sqrt{n}\),一類是大“因子”\(> \sqrt{n}\)
用\(a[i].first\)表示大因子集合,用\(a[i].second\)表示小因子集合
所有大因子相同的數顯然只能放在一個集合中,所以按照大因子的大小排序,這樣就可以把所有大因子相同的數放在一起算
在計算一段相同的大因子的數時,我們可以把\(f[s1][s2]\)拆成\(g1[s1][s2]\)和\(g2[s1][s2]\)分別表示這段數都放在集合\(1\)和集合\(2\)里的答案,\(g1,g2\)仍按照\(30pts\)的方程進行轉移即可
則\(f[s1][s2]=g1[s1][s2]+g2[s1][s2]-f[s1][s2]\)(\(f[s1][s2]\)加了兩遍)
最終答案\(ans=\sum\limits_{s1}\sum\limits_{s2}f[s1][s2]\)
注意\(s1=0\)和\(s2=0\)的情況
樹形 DP
顧名思義就是在樹上進行的\(DP\) ,在設計狀態是通常考慮子樹如何才能最優,然后考慮怎么用子樹更新根節點
常見於在樹上統計子樹和,在樹上選一些點滿足價值最大,花費最少的費用覆蓋所有點,樹上統計方案數問題等問題中
常見的規律:
一般來說樹形\(dp\)在設狀態轉移方程時都可以用\(f[i][]\)表示\(i\)這顆子樹怎么怎么樣的最優解,實現時一般都是用子樹更新父親(即從下向上更新),那么首先應該考慮的是一個一個子樹的更新父親還是把所有子樹都算完了在更新父親?這就要因題而異了,一般來說有兩種情況:
- 需要把所有子樹的信息都掌握之后再更新子樹的就需要把所有子樹都算完了在更新父親。
- 而像樹上背包這樣的問題就需要一個一個的更新,每次都用一個子樹更新已經更新完的子樹+父親,最后就可以將這一部分的子樹更新完了,再繼續往上更新,最后根節點就是答案。
其實上面的兩種情況可以總結成一種情況就是一個個子樹更新父親,一般來說第一種情況應用更多,也能解決第二情況的問題,只不過如果符合第二種情況的時候用第二種可以速度更快一點,畢竟你省了一遍循環嘛。
CF767C Garland
把一棵\(n\)個節點樹切成\(3\)部分,使得每一部分的點權和相等。
\(n \le 10^6\)
設所有的節點的權值和為\(sum\),\(f[u]\)表示以\(u\)為根的子樹的權值和
\(f[u]=a[u]+\sum\limits_{v\in son_u}f[v]\)
如果\(u\)有一個兒子\(v\)使得\(f[v]=\frac{sum}{3}\)則,我們將\(v\)切開,並且將\(f[u]-=f[v]\)
最后統計是否有兩個切點即可
P3942 將軍令
給定\(n\)個節點的樹,節點上可以放小隊,每個小隊可以控制距離該點不超過\(k\)的所有點,問最少放多少小隊能全部控制\(n\)個節點
\(n\le10^5,k\le 20\)
- \(5pts\)
直接輸出\(n\)
- \(45pts,k=1\)
和P2279 [HNOI2003]消防局的設立很類似,設\(f[i][0/1/2]\)表示\(i\)這個點被兒子\(0\),自己\(1\),被父親\(2\)控制
$f[u][0]=sum( \min (f[v][0],f[v][1]))- \min(0,\max(f[v][1]-f[v][0]) ) $
\(f[u][1]=sum(\min(f[v]))\)
\(f[u][2]=sum(\min(f[v][0],f[v][1]))\)
- \(75pts,k=2\)
和上面的一樣,只要在第二維再加兩個狀態,孫子和兒子全部控制但自己沒被控制\(3\)和孫子全被控制但是自己和兒子不全被控制\(4\)
- \(90pts\)
依舊再加狀態,不在贅述
- \(100pts\)
- 貪心\(100pts\)
這種題目有個結論,一定是在該點的\(k\)級祖先(若沒有則是根節點)
那么從下往上更新答案即可
代碼見P3942 將軍令
P3523 [POI2011]DYN-Dynamite
給一棵樹,書上有一些關鍵節點,要求你選m個點,使得關鍵節點到這些點中距離的最小值的最大值最小,求這個值
看完題目就感覺到濃濃的二分答案的味道,顯然外層有個二分答案
在樹上選 \(tot\) 個點,使得這 \(tot\) 個點與關鍵節點的最小距離不超過\(mid\),最小化 \(tot\)。
問題可以繼續轉化:
在樹上選 \(tot\) 個點,每個點可以覆蓋距離不超過\(mid\)的點, 最少幾個點能將關鍵點全部覆蓋
這就和將軍令一樣,變成最小點覆蓋問題了
\(f1[x]\):\(x\)的子樹中未被覆蓋的最遠的關鍵節點的距離
\(f2[x]\):\(x\)的子樹中最近選擇節點的距離
則\(f1,f2\)轉移
\(f1[x]=\max(f1[u])+1\)
\(f2[x]=\min(f2[u])+1\)
然后根據\(f1,f2\)的幾種情況,統計答案即可,這里就不寫了,自己看代碼吧(懶得寫了我不會
代碼見P3523 [POI2011]DYN-Dynamite
P3177 [HAOI2015]樹上染色
一棵\(n\)個點的數,將\(k\)個點染成黑色的,其他\(n-k\)個點是白色的,使黑點兩兩距離和加白點兩兩距離和最大
\(0\le n,k\le 2000\)
一開始想的是\(f[i][j]\)表示以\(i\)為根的子樹中有\(j\)個黑點的最大距離和
經過一番思考摸魚后,這樣似乎沒法轉移,主要問題在於沒法再確定\(i\)是黑是白的情況下計算貢獻
我們考慮一下怎么計算貢獻,對於兩個同色點之間的距離我們可以看成是路徑的和,路徑上的和就是路徑上邊的和,我們可以通過統計每個邊被同色點的路徑經過次數來統計最后答案
設\(k\)為當前子節點的子樹上已選擇的黑點數,顯然一條邊被經過的次數為:
\(tot=k\cdot(m-k)+(size[v]-k)\cdot(n-m-size[v]+k)\)
這樣上面的狀態就不太合適了,修改一下\(f[i][j]\)表示以\(i\)為根的子樹中有\(j\)個黑點對答案的最大貢獻
\(f[u][j]=\max(f[u][j],f[u][j-k]+f[to][k]+tot\cdot e[i].w)\)